@sap/cds-compiler 4.3.2 → 4.4.2

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 +39 -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 +44 -23
  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 +7 -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 +85 -39
  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 +260 -0
  29. package/lib/edm/annotations/edmJson.js +994 -0
  30. package/lib/edm/annotations/genericTranslation.js +82 -423
  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 +10 -3
  77. package/lib/transform/localized.js +1 -1
  78. package/lib/transform/parseExpr.js +73 -21
  79. package/lib/transform/translateAssocsToJoins.js +22 -15
  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
@@ -98,7 +98,9 @@ function transform4odataWithCsn(inputModel, options) {
98
98
  inspectRef,
99
99
  artifactRef,
100
100
  effectiveType,
101
- getFinalTypeInfo
101
+ getFinalTypeInfo,
102
+ dropDefinitionCache,
103
+ initDefinition,
102
104
  } = csnUtils;
103
105
 
104
106
  // are we working with structured OData or not
@@ -147,6 +149,8 @@ function transform4odataWithCsn(inputModel, options) {
147
149
  // TODO: handle artifact.projection instead of artifact.query correctly in future V2
148
150
  if (def.kind === 'entity' && def.projection) {
149
151
  def.query = { SELECT: def.projection };
152
+ dropDefinitionCache(def);
153
+ initDefinition(def);
150
154
  }
151
155
  }],
152
156
  { skipArtifact: isExternalServiceMember }
@@ -161,12 +165,17 @@ function transform4odataWithCsn(inputModel, options) {
161
165
  // rendering which may has to publish external definitions
162
166
  expandToFinalBaseType(csn, transformers, csnUtils, services, options);
163
167
 
168
+ // - Generate artificial draft fields on a structured CSN if requested, flattening and struct
169
+ // expansion do their magic including foreign key generation and annotation propagation.
170
+ generateDrafts(csn, options, services);
171
+
164
172
  // Check if structured elements and managed associations are compared in an expression
165
173
  // and expand these structured elements. This tuple expansion allows all other
166
174
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
167
175
  // If errors are detected, throwWithAnyError() will return from further processing
168
176
  expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
169
177
 
178
+
170
179
  if (!structuredOData) {
171
180
  expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
172
181
  const resolved = new WeakMap();
@@ -211,7 +220,6 @@ function transform4odataWithCsn(inputModel, options) {
211
220
  forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
212
221
 
213
222
  // Now all artificially generated things are in place
214
- // - Generate artificial draft fields if requested
215
223
  // TODO: should be done by the compiler - Check associations for valid foreign keys
216
224
  // TODO: check if needed at all: Remove '$projection' from paths in the element's ON-condition
217
225
  // - Check associations for:
@@ -219,7 +227,6 @@ function transform4odataWithCsn(inputModel, options) {
219
227
  // - structured types must not contain associations for OData V2
220
228
  // - Element must not be an 'array of' for OData V2 TODO: move to the validator
221
229
  // - Perform checks for exposed non-abstract entities and views - check media type and key-ness
222
- generateDrafts(csn, options, services)
223
230
 
224
231
  // Deal with all kind of annotations manipulations here
225
232
  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
 
@@ -809,8 +813,6 @@ function translateAssocsToJoins(model, inputOptions = {})
809
813
  if(rhs.mixin)
810
814
  {
811
815
  if (hasDollarSelfPrefix) {
812
- hasDollarSelfPrefix = true;
813
-
814
816
  /* Do the $projection resolution ONLY in own query not for referenced forward ON condition
815
817
  view YP as select from Y mixin ( toXP: association to XP on $projection.yid = toXP.xid; } into { yid };
816
818
  view XP as select from X mixin { toYP: association to YP on $self = toYP.toXP; } into { xid, toYP.elt };
@@ -1065,9 +1067,9 @@ function translateAssocsToJoins(model, inputOptions = {})
1065
1067
  tableAlias = [ aliasName, _artifact, _navigation ]
1066
1068
  path = [ { id: ..., _artifact: ... (unused) } ]
1067
1069
  */
1068
- function rewritePathsInExpression(node, getTableAliasAndPathSteps, env)
1070
+ function rewritePathsInFilterExpression(node, getTableAliasAndPathSteps, env)
1069
1071
  {
1070
- let innerEnv = {
1072
+ const innerEnv = {
1071
1073
  lead: env.lead,
1072
1074
  location: env.location,
1073
1075
  position: env.position,
@@ -1076,9 +1078,15 @@ function translateAssocsToJoins(model, inputOptions = {})
1076
1078
  callback: [
1077
1079
  function(pathNode) {
1078
1080
  if(checkPathDictionary(pathNode, env)) {
1079
- let [ tableAlias, path ] = getTableAliasAndPathSteps(pathNode);
1080
- let pathStr = path.map(ps => ps.id).join(pathDelimiter);
1081
- 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
+ }
1082
1090
  }
1083
1091
  } ]
1084
1092
  };
@@ -1317,8 +1325,8 @@ function translateAssocsToJoins(model, inputOptions = {})
1317
1325
  // we need to unambiguously identify the target side with the full assoc prefix.
1318
1326
  // If the path is on the target side, strip the prefix of and treat src/tgt
1319
1327
  // paths uniformly.
1320
- path = (env.assocStack && env.assocStack.stripAssocPrefix(path) || path);
1321
- let lead = env.art || env.lead;
1328
+ path = (env.assocStack?.stripAssocPrefix(path) || path);
1329
+ const lead = env.art || env.lead;
1322
1330
  path.forEach((ps) => {
1323
1331
  /* checks for all path steps */
1324
1332
  if(ps.args) {
@@ -1715,6 +1723,7 @@ function constructPathNode(pathSteps, alias, rewritten=true)
1715
1723
  o[k] = p[k];
1716
1724
  });
1717
1725
  setProp(o, '_artifact', p._artifact );
1726
+ setProp(o, '_navigation', p._navigation );
1718
1727
  return o; })
1719
1728
  };
1720
1729
 
@@ -1760,8 +1769,6 @@ function walkQuery(query, env)
1760
1769
  walk(query.having, env);
1761
1770
  env.location = 'OrderBy';
1762
1771
  walk(query.orderBy, env);
1763
- // outer orderBy's of anonymous union
1764
- walk(query.$orderBy, env);
1765
1772
  if(query.limit) {
1766
1773
  env.location = 'Limit';
1767
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.