@sap/cds-compiler 4.3.0 → 4.4.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 (81) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/lib/api/main.js +14 -24
  3. package/lib/api/options.js +1 -0
  4. package/lib/api/trace.js +38 -0
  5. package/lib/base/location.js +46 -1
  6. package/lib/base/message-registry.js +68 -16
  7. package/lib/base/messages.js +8 -3
  8. package/lib/checks/.eslintrc.json +1 -0
  9. package/lib/checks/actionsFunctions.js +1 -1
  10. package/lib/checks/annotationsOData.js +2 -2
  11. package/lib/checks/selectItems.js +4 -1
  12. package/lib/compiler/assert-consistency.js +3 -2
  13. package/lib/compiler/base.js +1 -1
  14. package/lib/compiler/builtins.js +25 -1
  15. package/lib/compiler/checks.js +6 -5
  16. package/lib/compiler/define.js +12 -10
  17. package/lib/compiler/extend.js +22 -22
  18. package/lib/compiler/finalize-parse-cdl.js +1 -1
  19. package/lib/compiler/generate.js +70 -53
  20. package/lib/compiler/kick-start.js +9 -5
  21. package/lib/compiler/populate.js +31 -22
  22. package/lib/compiler/propagator.js +6 -2
  23. package/lib/compiler/resolve.js +52 -17
  24. package/lib/compiler/shared.js +74 -38
  25. package/lib/compiler/tweak-assocs.js +64 -23
  26. package/lib/compiler/utils.js +40 -23
  27. package/lib/edm/.eslintrc.json +2 -0
  28. package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
  29. package/lib/edm/annotations/edmJson.js +994 -0
  30. package/lib/edm/annotations/genericTranslation.js +75 -421
  31. package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
  32. package/lib/edm/csn2edm.js +12 -5
  33. package/lib/edm/edm.js +14 -73
  34. package/lib/edm/edmPreprocessor.js +6 -0
  35. package/lib/gen/Dictionary.json +187 -16
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageLexer.interp +1 -1
  39. package/lib/gen/languageLexer.js +1129 -671
  40. package/lib/gen/languageParser.js +4285 -4283
  41. package/lib/json/from-csn.js +13 -18
  42. package/lib/json/to-csn.js +11 -6
  43. package/lib/language/antlrParser.js +0 -1
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/errorStrategy.js +95 -30
  46. package/lib/language/genericAntlrParser.js +21 -1
  47. package/lib/main.js +13 -3
  48. package/lib/model/csnRefs.js +42 -8
  49. package/lib/model/csnUtils.js +14 -2
  50. package/lib/model/enrichCsn.js +33 -5
  51. package/lib/model/revealInternalProperties.js +5 -0
  52. package/lib/modelCompare/compare.js +76 -14
  53. package/lib/modelCompare/utils/filter.js +19 -12
  54. package/lib/optionProcessor.js +2 -0
  55. package/lib/render/.eslintrc.json +1 -1
  56. package/lib/render/manageConstraints.js +1 -0
  57. package/lib/render/toHdbcds.js +3 -0
  58. package/lib/render/toRename.js +3 -1
  59. package/lib/render/toSql.js +46 -92
  60. package/lib/render/utils/common.js +76 -0
  61. package/lib/render/utils/delta.js +17 -3
  62. package/lib/sql-identifier.js +1 -1
  63. package/lib/transform/db/.eslintrc.json +1 -0
  64. package/lib/transform/db/applyTransformations.js +30 -4
  65. package/lib/transform/db/associations.js +22 -10
  66. package/lib/transform/db/backlinks.js +6 -2
  67. package/lib/transform/db/expansion.js +2 -2
  68. package/lib/transform/db/transformExists.js +13 -39
  69. package/lib/transform/draft/db.js +14 -3
  70. package/lib/transform/draft/odata.js +5 -18
  71. package/lib/transform/effective/associations.js +46 -15
  72. package/lib/transform/effective/main.js +7 -2
  73. package/lib/transform/effective/misc.js +43 -24
  74. package/lib/transform/effective/queries.js +20 -22
  75. package/lib/transform/effective/types.js +6 -2
  76. package/lib/transform/forOdata.js +5 -2
  77. package/lib/transform/localized.js +1 -1
  78. package/lib/transform/parseExpr.js +73 -21
  79. package/lib/transform/translateAssocsToJoins.js +24 -16
  80. package/lib/utils/term.js +2 -2
  81. package/package.json +2 -1
@@ -3,7 +3,7 @@
3
3
  const { setProp } = require('../../base/model');
4
4
  const flattening = require('../db/flattening');
5
5
  const {
6
- applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, cloneCsnNonDict,
6
+ applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, cloneCsnNonDict, forEachMember, applyTransformationsOnNonDictionary,
7
7
  } = require('../../model/csnUtils');
8
8
  const associations = require('../db/associations');
9
9
  const backlinks = require('../db/backlinks');
@@ -33,31 +33,62 @@ function turnAssociationsIntoUnmanaged( csn, options, csnUtils, { error, warning
33
33
  }, [ 'definitions', artifactName ]);
34
34
  });
35
35
  // Flatten out the fks and create the corresponding elements
36
- flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', true, csnUtils, { allowArtifact: a => a.kind === 'entity' });
36
+ flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', true, csnUtils, { allowArtifact: () => true, skipDict: {} });
37
37
 
38
38
  // Add the foreign keys also to the columns if the association itself was explicitly selected
39
39
  // TODO: Extend the expansion to also expand managed to their foreign
40
40
  // TODO: Remember where in .elements we had associations and do it then?
41
41
  applyTransformations(csn, {
42
- columns: (parent, prop, columns) => {
43
- const newColumns = [];
44
- for (const col of columns) {
45
- newColumns.push(col);
46
- const element = csnUtils.getElement(col);
47
- if (element && element.keys)
48
- element.keys.forEach(fk => addForeignKeyToColumns(fk, newColumns, col, options));
49
- }
50
- parent.columns = newColumns;
51
- },
52
- }, [], { allowArtifact: artifact => artifact.kind === 'entity' });
42
+ columns: expandManagedToFksInArray(),
43
+ groupBy: expandManagedToFksInArray(true),
44
+ orderBy: expandManagedToFksInArray(true),
45
+ }, [], { allowArtifact: artifact => artifact.query !== undefined || artifact.projection !== undefined });
53
46
 
54
- forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_'));
47
+ forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_', true));
48
+ applyTransformations(csn, {
49
+ elements: (_parent, prop, elements, path) => {
50
+ forEachMember(_parent, (element, elementName, _prop, elPath) => {
51
+ if (element.on) {
52
+ applyTransformationsOnNonDictionary(element, 'on', {
53
+ ref: (parent, __prop, ref) => {
54
+ if (ref[0] === '$self' && ref.length > 1 && !ref[1].startsWith('$')) // TODO: Is this safe?
55
+ parent.ref = ref.slice(1);
56
+ },
57
+ }, {}, elPath);
58
+ }
59
+ }, path);
60
+ },
61
+ });
55
62
  // Calculate the on-conditions from the .keys -
56
- associations.attachOnConditions(csn, csnUtils, '_');
63
+ associations.attachOnConditions(csn, csnUtils, '_', { allowArtifact: () => true }, options);
57
64
 
58
65
  return csn;
66
+
67
+ /**
68
+ * Expand managed associations in an array and insert them in-place
69
+ *
70
+ * If requested, leave out the assocs themselves
71
+ *
72
+ * @param {boolean} [killAssoc=false]
73
+ * @returns {Function} applyTransformationsCallback
74
+ */
75
+ function expandManagedToFksInArray( killAssoc = false ) {
76
+ return function expand(parent, prop, array, path) {
77
+ const newColumns = [];
78
+ for (let i = 0; i < array.length; i++) {
79
+ const col = array[i];
80
+ const element = csnUtils.getElement(col) || col.ref && csnUtils.inspectRef(path.concat(prop, i)).art;
81
+ if (!killAssoc || !element?.keys)
82
+ newColumns.push(col);
83
+ if (element?.keys)
84
+ element.keys.forEach(fk => addForeignKeyToColumns(fk, newColumns, col, options));
85
+ }
86
+ parent[prop] = newColumns;
87
+ };
88
+ }
59
89
  }
60
90
 
91
+
61
92
  /**
62
93
  * FKs need to be added to the .columns
63
94
  * @todo stolen from lib/transform/db/views.js
@@ -34,6 +34,8 @@ function effectiveCsn( model, options ) {
34
34
 
35
35
  const csn = cloneCsnNonDict(model, options);
36
36
 
37
+ delete csn.vocabularies; // must not be set for effective CSN
38
+
37
39
  const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, '_');
38
40
  queries.projectionToSELECTAndAddColumns(csn);
39
41
 
@@ -58,11 +60,11 @@ function effectiveCsn( model, options ) {
58
60
 
59
61
  messageFunctions.throwWithAnyError();
60
62
 
61
- const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils);
62
-
63
63
  // Expand a structured thing in: keys, columns, order by, group by
64
64
  expansion.expandStructureReferences(csn, options, '_', messageFunctions, csnUtils);
65
65
 
66
+ const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils);
67
+
66
68
  csnUtils = getUtils(csn, 'init-all');
67
69
 
68
70
  // Remove properties attached by validator - they do not "grow" as the model grows.
@@ -73,6 +75,9 @@ function effectiveCsn( model, options ) {
73
75
 
74
76
  resolveTypesInActionsAfterFlattening();
75
77
 
78
+ // ensure getElement works on flattened struct_assoc columns
79
+ csnUtils = getUtils(csn, 'init-all');
80
+
76
81
  processCalculatedElementsInEntities(csn);
77
82
  associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
78
83
  associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- forEachDefinition, forEachMemberRecursively, getArtifactDatabaseNameOf, getElementDatabaseNameOf, forEachMember,
4
+ forEachDefinition, forEachMemberRecursively, getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
5
5
  } = require('../../model/csnUtils');
6
6
  /**
7
7
  * Attach @cds.persistence.name to all artifacts and "things".
@@ -14,16 +14,21 @@ function attachPersistenceName( csn, options, csnUtils ) {
14
14
  const { addStringAnnotationTo } = csnUtils;
15
15
 
16
16
  forEachDefinition(csn, (artifact, artifactName) => {
17
- if (artifact.kind === 'entity') {
18
- addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
17
+ addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
19
18
 
20
- forEachMemberRecursively(artifact, (member, memberName) => addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member), [ 'definitions', artifactName ]);
21
- }
19
+ forEachMemberRecursively(artifact, (member, memberName) => addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member), [ 'definitions', artifactName ]);
22
20
  });
23
21
  }
24
22
 
25
- const artifactPropertiesToRemove = [ 'includes' ];
26
- const memberPropertiesToRemove = [ 'localized', 'enum', 'keys' ];
23
+ /**
24
+ * Delete the given prop from parent.
25
+ *
26
+ * @param {object} parent
27
+ * @param {string|number} prop
28
+ */
29
+ function killProp( parent, prop ) {
30
+ delete parent[prop];
31
+ }
27
32
 
28
33
  /**
29
34
  * Remove definitions from the CSN:
@@ -36,26 +41,40 @@ const memberPropertiesToRemove = [ 'localized', 'enum', 'keys' ];
36
41
  * @param {CSN.Model} csn
37
42
  * @todo Callback-like architecture and merge with persistence name?
38
43
  */
39
- function removeDefinitionsAndProperties( csn ) {
40
- forEachDefinition(csn, (artifact, artifactName) => {
41
- if (artifact.kind === 'aspect' || artifact.kind === 'type') {
42
- delete csn.definitions[artifactName];
43
- }
44
- else {
45
- if (artifact['@cds.persistence.skip'] === 'if-unused')
44
+ function _removeDefinitionsAndProperties( csn ) {
45
+ const killers = {
46
+ $ignore: (a, b, c, path, parentParent) => {
47
+ const tail = path[path.length - 1];
48
+ delete parentParent[tail];
49
+ },
50
+ kind: (artifact, a, b, path) => {
51
+ if (artifact.kind === 'aspect' || artifact.kind === 'type')
52
+ delete csn.definitions[path[1]];
53
+
54
+ else if (artifact['@cds.persistence.skip'] === 'if-unused')
46
55
  artifact['@cds.persistence.skip'] = false;
47
- for (const prop of artifactPropertiesToRemove)
48
- delete artifact[prop];
49
-
50
- forEachMember(artifact, (member) => {
51
- for (const prop of memberPropertiesToRemove)
52
- delete member[prop];
53
- });
54
- }
55
- });
56
+ },
57
+ // Still used in flattenStructuredElements - in db/flattening.js
58
+ _flatElementNameWithDots: killProp,
59
+ // Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
60
+ // to not copy the .length property if it was only set via default
61
+ $default: killProp,
62
+ // Set when we turn UUID into String, checked during generateDraftForHana
63
+ $renamed: killProp,
64
+ // Set when we remove .key from temporal things, used in localized.js
65
+ $key: killProp,
66
+ includes: killProp,
67
+ localized: killProp,
68
+ enum: killProp,
69
+ keys: killProp,
70
+ excluding: killProp, // * is resolved, so has no effect anymore
71
+ };
72
+
73
+ applyTransformations(csn, killers, [], { skipIgnore: false });
56
74
  }
57
75
 
76
+
58
77
  module.exports = {
59
78
  attachPersistenceName,
60
- removeDefinitionsAndProperties,
79
+ removeDefinitionsAndProperties: _removeDefinitionsAndProperties,
61
80
  };
@@ -11,28 +11,26 @@ const { forEachDefinition, applyTransformationsOnNonDictionary } = require('../.
11
11
  function projectionToSELECTAndAddColumns( csn ) {
12
12
  const redoProjections = [];
13
13
  forEachDefinition(csn, (artifact) => {
14
- if (artifact.kind === 'entity') {
15
- if (artifact.projection) {
16
- if (!artifact.projection.columns)
17
- artifact.projection.columns = [ '*' ];
18
- artifact.query = { SELECT: artifact.projection };
19
- delete artifact.projection;
20
- redoProjections.push(() => {
21
- if (artifact.query) {
22
- artifact.projection = artifact.query.SELECT;
23
- delete artifact.query;
24
- if (artifact.$syntax === 'projection')
25
- delete artifact.$syntax;
26
- }
27
- });
28
- }
29
- else if (artifact.query) {
30
- applyTransformationsOnNonDictionary(artifact, 'query', {
31
- SELECT: (parent, prop, SELECT) => {
32
- SELECT.columns ??= [ '*' ];
33
- },
34
- });
35
- }
14
+ if (artifact.projection) {
15
+ if (!artifact.projection.columns)
16
+ artifact.projection.columns = [ '*' ];
17
+ artifact.query = { SELECT: artifact.projection };
18
+ delete artifact.projection;
19
+ redoProjections.push(() => {
20
+ if (artifact.query) {
21
+ artifact.projection = artifact.query.SELECT;
22
+ delete artifact.query;
23
+ if (artifact.$syntax === 'projection')
24
+ delete artifact.$syntax;
25
+ }
26
+ });
27
+ }
28
+ else if (artifact.query) {
29
+ applyTransformationsOnNonDictionary(artifact, 'query', {
30
+ SELECT: (parent, prop, SELECT) => {
31
+ SELECT.columns ??= [ '*' ];
32
+ },
33
+ });
36
34
  }
37
35
  });
38
36
 
@@ -3,6 +3,7 @@
3
3
  const {
4
4
  cloneCsnNonDict, applyTransformations, applyTransformationsOnNonDictionary, cloneCsnDictionary, applyTransformationsOnDictionary,
5
5
  } = require('../../model/csnUtils');
6
+ const { forEachKey } = require('../../utils/objectUtils');
6
7
 
7
8
  /**
8
9
  * Resolve all references to structured types in entities to the underlying elements.
@@ -33,7 +34,7 @@ function resolveTypes( csn, csnUtils ) {
33
34
  later.push({ [artifactName]: artifact });
34
35
  else if (artifact.actions)
35
36
  later.push(artifact.actions);
36
- } ], { skipDict: { actions: true } });
37
+ } ], { skipDict: { actions: true }, processAnnotations: true });
37
38
 
38
39
  return function resolveTypesInActions() {
39
40
  later.forEach(action => applyTransformationsOnDictionary(action, { type: parent => resolveType(parent) }));
@@ -64,7 +65,10 @@ function resolveTypes( csn, csnUtils ) {
64
65
  delete parent.type;
65
66
  }
66
67
  else if (final?.type) {
67
- parent.type = final.type;
68
+ forEachKey(final, (key) => { // copy `type` + properties (default, etc.)
69
+ if (parent[key] === undefined || key === 'type')
70
+ parent[key] = final[key];
71
+ });
68
72
  }
69
73
 
70
74
  // Drill down - there might be other type references
@@ -161,12 +161,17 @@ function transform4odataWithCsn(inputModel, options) {
161
161
  // rendering which may has to publish external definitions
162
162
  expandToFinalBaseType(csn, transformers, csnUtils, services, options);
163
163
 
164
+ // - Generate artificial draft fields on a structured CSN if requested, flattening and struct
165
+ // expansion do their magic including foreign key generation and annotation propagation.
166
+ generateDrafts(csn, options, services);
167
+
164
168
  // Check if structured elements and managed associations are compared in an expression
165
169
  // and expand these structured elements. This tuple expansion allows all other
166
170
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
167
171
  // If errors are detected, throwWithAnyError() will return from further processing
168
172
  expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
169
173
 
174
+
170
175
  if (!structuredOData) {
171
176
  expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
172
177
  const resolved = new WeakMap();
@@ -211,7 +216,6 @@ function transform4odataWithCsn(inputModel, options) {
211
216
  forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
212
217
 
213
218
  // Now all artificially generated things are in place
214
- // - Generate artificial draft fields if requested
215
219
  // TODO: should be done by the compiler - Check associations for valid foreign keys
216
220
  // TODO: check if needed at all: Remove '$projection' from paths in the element's ON-condition
217
221
  // - Check associations for:
@@ -219,7 +223,6 @@ function transform4odataWithCsn(inputModel, options) {
219
223
  // - structured types must not contain associations for OData V2
220
224
  // - Element must not be an 'array of' for OData V2 TODO: move to the validator
221
225
  // - Perform checks for exposed non-abstract entities and views - check media type and key-ness
222
- generateDrafts(csn, options, services)
223
226
 
224
227
  // Deal with all kind of annotations manipulations here
225
228
  const skipPersNameKinds = {'service':1, 'context':1, 'namespace':1, 'annotation':1, 'action':1, 'function':1};
@@ -648,7 +648,7 @@ function _addLocalizationViews(csn, options, config) {
648
648
  filter: (name) => name.startsWith('localized.'),
649
649
  notFound(name, index) {
650
650
  if (!ignoreUnknownExtensions) {
651
- messageFunctions.message('anno-undefined-art', [ 'extensions', index ],
651
+ messageFunctions.message('ext-undefined-def', [ 'extensions', index ],
652
652
  { art: name });
653
653
  }
654
654
  },
@@ -34,12 +34,13 @@
34
34
  *
35
35
  * @param {any} xpr A JSON object.
36
36
  * @param {Object} state Object
37
- * anno: Don't eliminate arrays with single entry in statetations (TODO?) as they are collections
37
+ * anno: Don't eliminate arrays with single entry in expressions (TODO?) as they are collections
38
38
  * array: Bias AST representation.
39
39
  * nary: return n-ary or binary tree
40
40
  */
41
41
 
42
- function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
42
+ function parseExpr(xpr, state = { array: true, nary: false }) {
43
+ state.anno = 0;
43
44
  // Notes:
44
45
  // - Variables `s` and `e` are used as index variables into `xpr`s for start and end.
45
46
  // - xpr's are our CSN expressions, see <https://cap.cloud.sap/docs/cds/cxn>
@@ -47,7 +48,28 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
47
48
  return parseExprInt(xpr, state);
48
49
 
49
50
  function parseExprInt(xpr, state) {
50
- return conditionOR(...CaseWhen(xpr), state);
51
+ return conditionOR(...CaseWhen(Cast(xpr, state)), state);
52
+ }
53
+
54
+ function Cast(xpr, state) {
55
+ if(xpr != null && !state.array) {
56
+ if(Array.isArray(xpr))
57
+ return xpr.map(x => Cast(x, state));
58
+ if(typeof xpr === 'object') {
59
+ const castKeys = Object.keys(xpr).filter(k => k !== 'cast');
60
+ if(xpr.cast != null && castKeys.length === 1) {
61
+ return { 'cast': [ xpr.cast, { [castKeys[0]]: xpr[castKeys[0]] } ] };
62
+ }
63
+ else {
64
+ for(let n in xpr) {
65
+ // xpr could be an array with polluted prototype
66
+ if (Object.hasOwnProperty.call(xpr, n))
67
+ xpr[n] = Cast(xpr[n], state)
68
+ }
69
+ }
70
+ }
71
+ }
72
+ return xpr;
51
73
  }
52
74
 
53
75
  function CaseWhen(xpr) {
@@ -95,11 +117,11 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
95
117
 
96
118
  while(xpr[whenPos] !== 'when' && whenPos < endPos) whenPos++;
97
119
  if(xpr[whenPos] === 'when' && whenPos - (casePos+1) >= 1) {
98
- const arg = xpr.slice(casePos+1, whenPos)
120
+ const caseExpr = xpr.slice(casePos+1, whenPos)
99
121
  if(state.array)
100
- caseTree.push(arg);
122
+ caseTree.push(caseExpr);
101
123
  else
102
- caseTree.case.push(arg);
124
+ caseTree.case.push(caseExpr.length === 1 ? caseExpr[0] : caseExpr);
103
125
  }
104
126
 
105
127
  while(xpr[whenPos] === 'when') {
@@ -112,11 +134,11 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
112
134
  let thenPos = whenPos+1;
113
135
  while(xpr[thenPos] !== 'then' && thenPos < endPos) thenPos++;
114
136
  if(xpr[thenPos] === 'then') {
115
- const when = xpr.slice(whenPos+1, thenPos);
137
+ const whenExpr = xpr.slice(whenPos+1, thenPos);
116
138
  if(state.array)
117
- caseTree.push(when);
139
+ caseTree.push(whenExpr);
118
140
  else
119
- when.when.push(when.length === 1 ? when[0] : when);
141
+ when.when.push(whenExpr.length === 1 ? whenExpr[0] : whenExpr);
120
142
  }
121
143
 
122
144
  whenPos = thenPos+1;
@@ -188,17 +210,19 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
188
210
  function compareTerm(xpr, s, e, state) {
189
211
  if(Array.isArray(xpr)) {
190
212
  let i = s;
213
+ let not = false;
214
+ let between;
191
215
  while(i < e && xpr[i] !== 'between') i++;
192
216
  const b = i < e ? i : -1;
193
217
  while(i < e && xpr[i] !== 'and') i++;
194
218
  const a = i < e ? i : -1;
195
219
  if(b >= 0) {
196
220
  let token = [ 'between' ];
197
- const not = (xpr[b-1] === 'not');
221
+ not = (xpr[b-1] === 'not');
198
222
  if(not)
199
223
  token.splice(0,0, 'not');
200
224
  const expr = expression(xpr, s, not ? b-1 : b, state);
201
- const between = state.array
225
+ between = state.array
202
226
  ? [ expr, ...token ]
203
227
  : { 'between': [ expr ] };
204
228
  if(a >= 0) {
@@ -217,6 +241,9 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
217
241
  else
218
242
  between.between.push(unspec);
219
243
  }
244
+ if(not && !state.array) {
245
+ between = { 'not': between }
246
+ }
220
247
  return between;
221
248
  }
222
249
  }
@@ -263,7 +290,10 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
263
290
  function unary(xpr, s, e, state) {
264
291
  if(Array.isArray(xpr)) {
265
292
  if(xpr[s] === '+' || xpr[s] === '-' || (!state.array && xpr[s] === 'new')) {
266
- return [ xpr[s], unary(xpr, s+1, e, state) ];
293
+ if(state.array)
294
+ return [ xpr[s], unary(xpr, s+1, e, state) ];
295
+ else
296
+ return { [xpr[s]]: unary(xpr, s+1, e, state) };
267
297
  }
268
298
  }
269
299
  return terminal(xpr, s, e, state);
@@ -277,7 +307,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
277
307
  'xpr', 'on', 'where', 'orderBy', 'groupBy', 'having' ];
278
308
 
279
309
  if(Array.isArray(xpr) && xpr.length > 0) {
280
- if(e-s <= 1 && state.anno === 0)
310
+ if(e-s <= 1 && state.anno === 0 && typeof xpr[e-1] !== 'string')
281
311
  return parseExprInt(xpr[e-1], state);
282
312
  else
283
313
  return xpr.slice(s, e).map(ix => parseExprInt(ix, state));
@@ -311,28 +341,50 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
311
341
  }
312
342
 
313
343
  function binaryExpr(xpr, token, next, s, e, state) {
314
- const expr = [];
344
+ const naryExpr = [];
345
+ let not = false;
315
346
  if (Array.isArray(xpr)) {
316
347
  let [tl, p] = findToken(s, e);
317
348
  if (p >= 0) {
318
349
  let lhs = next(xpr, s, p, state);
319
- expr.push(lhs);
350
+ naryExpr.push(lhs);
320
351
  let op = xpr.slice(p, p+tl);
321
352
  s = p+tl;
322
353
  [tl, p] = findToken(s, e);
323
354
  while(p >= 0) {
324
355
  let rhs = next(xpr, s, p, state);
325
- expr.push(...op, rhs);
326
- lhs = state.array ? [ lhs, ...op, rhs ] : { [op.join('')]: [lhs, rhs] };
356
+ naryExpr.push(...op, rhs);
357
+ if(state.array)
358
+ lhs = [ lhs, ...op, rhs ];
359
+ else {
360
+ not = op.length > 1 && op[0] === 'not';
361
+ if(not)
362
+ op = op.slice(1);
363
+ lhs = (not
364
+ ? { 'not': { [op.join('')]: [lhs, rhs] } }
365
+ : { [op.join('')]: [lhs, rhs] });
366
+ }
327
367
  op = xpr.slice(p, p+tl);
328
368
  s = p+tl;
329
369
  [tl, p] = findToken(s, e);
330
370
  }
331
- expr.push(...op, next(xpr, s, e, state));
371
+
372
+ let rhs = next(xpr, s, e, state);
373
+ if(Array.isArray(rhs) && rhs.length === 0)
374
+ rhs = undefined;
375
+ naryExpr.push(...op, rhs);
376
+
332
377
  if (state.array)
333
- return (state.nary ? expr : [ lhs, ...op, next(xpr, s, e, state) ])
334
- else
335
- return { [op.join('')]: [lhs, next(xpr, s, e, state)] };
378
+ return (state.nary ? naryExpr : [ lhs, ...op, rhs ])
379
+ else {
380
+ not = op.length > 1 && op[0] === 'not';
381
+ if(not)
382
+ op = op.slice(1);
383
+ return (not
384
+ ? { 'not': { [op.join('')]: [ lhs, rhs ] } }
385
+ : { [op.join('')]: [ lhs, rhs ] });
386
+
387
+ }
336
388
  }
337
389
  }
338
390
  return next(xpr, s, e, state);
@@ -17,7 +17,9 @@ function translateAssocsToJoinsCSN(csn, options){
17
17
  // Do not re-complain about localized
18
18
  const compileOptions = { ...options, $skipNameCheck: true };
19
19
  delete compileOptions.csnFlavor;
20
- // console.log('CSN passed by A2J to compiler:',JSON.stringify(csn,null,2))
20
+
21
+ //require('fs').writeFileSync('./csninput_a2j.json', JSON.stringify(csn, null,2))
22
+ //console.log('CSN passed by A2J to compiler:',JSON.stringify(csn,null,2))
21
23
  const model = recompileX(csn, compileOptions);
22
24
  timetrace.stop('A2J: Recompiling model');
23
25
  timetrace.start('A2J: Translating associations to joins');
@@ -48,7 +50,9 @@ function translateAssocsToJoinsCSN(csn, options){
48
50
  // If A2J reports error - end! Continuing with a broken CSN makes no sense
49
51
  makeMessageFunction(model, options).throwWithAnyError();
50
52
  // FIXME: Move this somewhere more appropriate
51
- return compactModel(model, compileOptions);
53
+ const newCsn = compactModel(model, compileOptions);
54
+ //require('fs').writeFileSync('./csnoutput_a2j.json', JSON.stringify(newCsn, null,2))
55
+ return newCsn;
52
56
  }
53
57
 
54
58
  function translateAssocsToJoins(model, inputOptions = {})
@@ -444,7 +448,7 @@ function translateAssocsToJoins(model, inputOptions = {})
444
448
  {
445
449
  // Filter conditions are unique for each JOIN, they don't need to be copied
446
450
  let filter = childQat._filter;
447
- rewritePathsInExpression(filter, function(pathNode) {
451
+ rewritePathsInFilterExpression(filter, function(pathNode) {
448
452
  return [ /* tableAlias=> */ constructTableAliasPathStep(childQat.$QA),
449
453
  /* filterPath=> */ pathNode.path ]; // eslint-disable-line indent-legacy
450
454
  }, env);
@@ -490,7 +494,7 @@ function translateAssocsToJoins(model, inputOptions = {})
490
494
  {
491
495
  // Filter conditions are unique for each JOIN, they don't need to be copied
492
496
  let filter = assocQAT._filter;
493
- rewritePathsInExpression(filter, function(pathNode) {
497
+ rewritePathsInFilterExpression(filter, function(pathNode) {
494
498
  return [ tgtTableAlias, pathNode.path ];
495
499
  }, env);
496
500
 
@@ -733,7 +737,8 @@ function translateAssocsToJoins(model, inputOptions = {})
733
737
  // this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
734
738
  if(expr.op) {
735
739
  let x = clone(expr);
736
- x.args = expr.args.map(cloneOnCondition);
740
+ if(expr.args)
741
+ x.args = expr.args.map(cloneOnCondition);
737
742
  return x;
738
743
  }
739
744
 
@@ -808,8 +813,6 @@ function translateAssocsToJoins(model, inputOptions = {})
808
813
  if(rhs.mixin)
809
814
  {
810
815
  if (hasDollarSelfPrefix) {
811
- hasDollarSelfPrefix = true;
812
-
813
816
  /* Do the $projection resolution ONLY in own query not for referenced forward ON condition
814
817
  view YP as select from Y mixin ( toXP: association to XP on $projection.yid = toXP.xid; } into { yid };
815
818
  view XP as select from X mixin { toYP: association to YP on $self = toYP.toXP; } into { xid, toYP.elt };
@@ -1064,9 +1067,9 @@ function translateAssocsToJoins(model, inputOptions = {})
1064
1067
  tableAlias = [ aliasName, _artifact, _navigation ]
1065
1068
  path = [ { id: ..., _artifact: ... (unused) } ]
1066
1069
  */
1067
- function rewritePathsInExpression(node, getTableAliasAndPathSteps, env)
1070
+ function rewritePathsInFilterExpression(node, getTableAliasAndPathSteps, env)
1068
1071
  {
1069
- let innerEnv = {
1072
+ const innerEnv = {
1070
1073
  lead: env.lead,
1071
1074
  location: env.location,
1072
1075
  position: env.position,
@@ -1075,9 +1078,15 @@ function translateAssocsToJoins(model, inputOptions = {})
1075
1078
  callback: [
1076
1079
  function(pathNode) {
1077
1080
  if(checkPathDictionary(pathNode, env)) {
1078
- let [ tableAlias, path ] = getTableAliasAndPathSteps(pathNode);
1079
- let pathStr = path.map(ps => ps.id).join(pathDelimiter);
1080
- replaceNodeContent(pathNode, constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
1081
+ const head = pathNode.path[0];
1082
+ if(head._navigation?.kind === '$self') {
1083
+ substituteDollarSelf(pathNode);
1084
+ }
1085
+ else {
1086
+ const [ tableAlias, path ] = getTableAliasAndPathSteps(pathNode);
1087
+ const pathStr = path.map(ps => ps.id).join(pathDelimiter);
1088
+ replaceNodeContent(pathNode, constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
1089
+ }
1081
1090
  }
1082
1091
  } ]
1083
1092
  };
@@ -1316,8 +1325,8 @@ function translateAssocsToJoins(model, inputOptions = {})
1316
1325
  // we need to unambiguously identify the target side with the full assoc prefix.
1317
1326
  // If the path is on the target side, strip the prefix of and treat src/tgt
1318
1327
  // paths uniformly.
1319
- path = (env.assocStack && env.assocStack.stripAssocPrefix(path) || path);
1320
- let lead = env.art || env.lead;
1328
+ path = (env.assocStack?.stripAssocPrefix(path) || path);
1329
+ const lead = env.art || env.lead;
1321
1330
  path.forEach((ps) => {
1322
1331
  /* checks for all path steps */
1323
1332
  if(ps.args) {
@@ -1714,6 +1723,7 @@ function constructPathNode(pathSteps, alias, rewritten=true)
1714
1723
  o[k] = p[k];
1715
1724
  });
1716
1725
  setProp(o, '_artifact', p._artifact );
1726
+ setProp(o, '_navigation', p._navigation );
1717
1727
  return o; })
1718
1728
  };
1719
1729
 
@@ -1759,8 +1769,6 @@ function walkQuery(query, env)
1759
1769
  walk(query.having, env);
1760
1770
  env.location = 'OrderBy';
1761
1771
  walk(query.orderBy, env);
1762
- // outer orderBy's of anonymous union
1763
- walk(query.$orderBy, env);
1764
1772
  if(query.limit) {
1765
1773
  env.location = 'Limit';
1766
1774
  walk(query.limit.rows, env);
package/lib/utils/term.js CHANGED
@@ -14,8 +14,8 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const stderrHasColor = process.stderr.isTTY;
18
- const stdoutHasColor = process.stdout.isTTY;
17
+ const stderrHasColor = process.stderr?.isTTY;
18
+ const stdoutHasColor = process.stdout?.isTTY;
19
19
 
20
20
  // Note: We require both stderr and stdout to be TTYs, as we don't
21
21
  // know (in our exported functions) where the text will end up.