Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
3
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 1.1.1 to 1.1.3

lib/gen/JSON.interp

28

bin/cdsc.js

@@ -38,3 +38,3 @@ #!/usr/bin/env node

.option('@@', 'Generation options (can be combined, default if none given is "--to-csn --out -")')
.option('@@', 'Generation options (can be combined, default if none given is "--to-csn client --out -")')
.option('-o, --out <dir>', 'Place generated files in directory <dir>, default is "-" for <stdout>')

@@ -102,9 +102,15 @@ .option('-H, --to-hana <flags>', `Generate HANA CDS source files, <flags> can be a comma-separated

style combination of property file and modified model
prop : Generate property file with annotation text
prop : Generate property file with annotation text
ui5 : Generate model with placeholders and property file with
annotation text`,
verifyI18nOption)
.option(' --to-csn <flavor>', `Generate original model as CSN to "csn.json", <flavor> can be either
"client" (default) or "gensrc"
client : Generate standard CSN consumable by clients and backends
gensrc : Generate CSN specifically for use as a source, e.g. for
combination with additional extend/annotate statements,
but not suitable for consumption by clients or backends`,
verifyToCsnOption)
.option('-l, --lint-mode', `Generate nothing, just produce single-file error messages if any (for
use by editors)`)
.option(' --to-csn', 'Generate original model as CSN to "csn.json"')
.option('@@', 'Backward compatibility options (deprecated, do not use)')

@@ -131,5 +137,2 @@ .option(' --oldstyle-self', 'Allow "self" alternatively to "$self" (implied by --tnt-flavor)') // FIXME: We should get rid of that

.option(' --re-augmented', 'Re-augmented CSN and error output') // FIXME: What does that mean/do? Isn't that what --extra-augment is supposed to do?
.option(' --old-propagate', 'Use old propagation logic')
.option(' --disable-propagate',`Do not propagate properties with "--to-csn" (makes result re-usable
with extensions)`) // FIXME: Should probably become a flag for '--to-csn'

@@ -221,4 +224,2 @@ .option('@@', 'Table renaming (tentative, subject to change, requires "--beta-mode")')

reAugmented : program.reAugmented,
oldPropagate : program.oldPropagate,
disablePropagate : program.disablePropagate, // TODO: how to handle this?
toRename : program.toRename,

@@ -445,2 +446,8 @@ }

// Check the value of --to-csn for legal values. Return the value as an object with sub-options.
function verifyToCsnOption(value) {
verifyFlags(['client', 'gensrc'], value, '--to-csn', 'flavor');
return (value == 'gensrc') ? { gensrc: true } : undefined;
}
// Executes a command line that has been translated to 'options' (what to do) and 'args' (which files)

@@ -700,7 +707,4 @@ function executeCommandLine(options, args) {

}
else if (options.newCsn) {
writeToFileOrDisplay(options.out, name + '.json', compiler.compactModel(model), true);
}
else {
writeToFileOrDisplay(options.out, name + '.json', compiler.compactSortedJson(model), true);
writeToFileOrDisplay(options.out, name + '.json', compiler.toCsn(model), true);
}

@@ -707,0 +711,0 @@ }

# ChangeLog for cdx compiler and backends
## Version 1.1.3
Features
* A `;` is now always optional before `}` and more often optional after a `}`.
Changes
* In `toOdata()` for v2, in the edmx the
**names of bound actions and functions now are prefixed with the corresponding entity's name**
in order to disambiguate actions and functions with the same name at two or more entities.
The corresponding implementation code in the CDS runtime needs to be adapted.
* Check `redirected to` target.
Fixes
* Make the compiler more robust wrt/ parse errors and redefinitions.
* Correctly propagate properties inside `returns` and `items`.
* Some corrections to EDM `ActionImport` and `FunctionImport` in ODATA V2 and V4.
* Generate correct joins for mixin associations that are traversed with different filters.
* Generate joins not only for views, but also for projections.
* For entities annotated with `@odata.draft.enabled`, make all non-key fields nullable in
`toOdata()`.
## Version 1.1.2
Features
* Allow reserved names for annotations/properties in assignments.
* Allow final `,` for much more "lists" (e.g. arguments).
* It is now possible to omit the select list in a view definition,
which is the same as writing `select from <name> {*}`.
* Allow `array of` as type spec for a parameter definition.
* SQL generation for sqlite now supports a mode where associations are resolved
to joins.
Changes
* Improved messages for syntax errors.
* `where` now is a reserved keyword and so cannot be used anymore as name at many places.
Fixes
* In `toOdata()` with the `hdbcds` naming convention, the value of the `@cds.persistence.name`
annotation now uses `.` rather than `_` as separator for the names of flattened structured
entity elements.
* Numeric values in OData annotations are now correctly mapped to edmx.
## Version 1.1.1

@@ -4,0 +46,0 @@

@@ -22,3 +22,3 @@ 'use strict';

const alerts = require('./base/alerts');
const propagator = require('./compiler/propagator');
var { CompilationError, sortMessages } = require('./base/messages');

@@ -63,6 +63,2 @@ // Transform an augmented CSN 'model' into HANA-compatible CDS source.

function toHana(model, options) {
// Must perform propagation now if it was disabled for the original CSN output
if (model.options.disablePropagate) {
model = propagator.propagateAssignments(model);
}
// Optional wrapper?

@@ -158,6 +154,2 @@ if (options && !options.toHana) {

function toOdata(model, options) {
// Must perform propagation now if it was disabled for the original CSN output
if (model.options.disablePropagate) {
model = propagator.propagateAssignments(model);
}
const { error, signal } = alerts(model);

@@ -251,7 +243,2 @@ // Optional wrapper?

function toCdl(model, options) {
// Must perform propagation now if it was disabled for the original CSN output
// FIXME: Hmm, or maybe we don't - this might even benefit from not propagating anything ...
if (model.options.disablePropagate) {
model = propagator.propagateAssignments(model);
}
// Merge options with those from model

@@ -291,6 +278,2 @@ options = mergeOptions({ toCdl : true }, model.options, options);

function toSwagger(model, options) {
// Must perform propagation now if it was disabled for the original CSN output
if (model.options.disablePropagate) {
model = propagator.propagateAssignments(model);
}
// Optional wrapper?

@@ -344,6 +327,2 @@ if (options && !options.toSwagger) {

function toSql(model, options) {
// Must perform propagation now if it was disabled for the original CSN output
if (model.options.disablePropagate) {
model = propagator.propagateAssignments(model);
}
// Optional wrapper?

@@ -414,6 +393,2 @@ if (options && !options.toSql) {

// Must perform propagation now if it was disabled for the original CSN output
if (model.options.disablePropagate) {
model = propagator.propagateAssignments(model);
}
// Optional wrapper?

@@ -463,4 +438,2 @@ if (options && !options.toRename) {

function toI18n(model, options) {
// No propagation required (actually, we are better off better without it)
// Optional wrapper?

@@ -486,10 +459,23 @@ if (options && !options.toI18n) {

// options : {
// testMode : if true, the result is extra-stable for automated tests (sorted, no 'version')
// testMode : if true, the result is extra-stable for automated tests (sorted, no 'version')
// newCsn : if true, CSN is returned in the (well-defined) new format '0.1.99' planned for future versions
// (default is the old format '0.1.0')
// toCsn.gensrc : if true, the result CSN is only suitable for use as a source, e.g. for combination with
// additional extend/annotate statements, but not for consumption by clients or backends
// (default is to produce 'client' CSN with all properties propagated and inferred as required
// by consumers and backends)
// }
// Options provided here are merged with (and take precedence over) options from 'model'.
// Throws a CompilationError on errors.
function toCsn(model, options) {
// Propagation has been performed (or not) by the compiler - leave that as it was.
const { error, signal } = alerts(model);
// Can't have an optional wrapper here because 'testMode' and 'newCsn' are global options
// Merge options with those from model
options = mergeOptions({ toCsn : true }, model.options, options);
if (options.toCsn.gensrc && !options.newCsn) {
signal(error`CSN in "gensrc" flavor can only be generated as new-style CSN (option "newCsn" required)`);
throw new CompilationError(sortMessages(model.messages), model);
}
return options.newCsn ? compactModel(model)

@@ -496,0 +482,0 @@ : (options.testMode ? compactSorted(model) : compact(model));

@@ -5,2 +5,16 @@ // Functions and classes for syntax messages

// For messageIds, where no severity has been provided via code (central def)
const standardSeverities = {
'syntax-anno-after-struct': 'Warning',
'syntax-anno-after-enum': 'Warning',
'syntax-anno-after-params': 'Warning', // very ugly!
}
// For messageIds, where no text has been provided via code (central def)
const standardTexts = {
'syntax-anno-after-struct': 'Avoid annotation assignments after structure definitions',
'syntax-anno-after-enum': 'Avoid annotation assignments after enum definitions',
'syntax-anno-after-params': 'Avoid annotation assignments after parameters',
}
function hasErrors( messages ) {

@@ -83,6 +97,10 @@ return messages && messages.some( m => m.severity === 'Error' );

return function message( id, location, params = {}, severity = undefined, texts = undefined ) {
let s = normalizedSeverity( severity ); // as specified
if (!severity) // TODO: check that they are always eq per messageId
severity = standardSeverities[id];
let s = normalizedSeverity( severity );
if ((s !== 'Error' || severity instanceof Array) && id && id in config )
s = normalizedSeverity( config[id] );
let text = (typeof params === 'string') ? params : messageText( texts, params );
let text = (typeof params === 'string')
? params
: messageText( texts || standardTexts[id], params );
let msg = new CompileMessage( location, text, s, id );

@@ -98,2 +116,4 @@ model.messages.push( msg );

art: transformArg,
code: n => '`' + n + '`',
name: n => msgName(n),
id: n => msgName(n)

@@ -118,3 +138,3 @@ };

return msgName( arg );
r[''] = 'elem'; // text variant 'elem'
r['#'] = 'elem'; // text variant 'elem'
r.elem = msgName( arg.element );

@@ -132,3 +152,3 @@ return msgName( arg, true );

}
let variant = args[''];
let variant = args['#'];
return replaceInString( variant && texts[ variant ] || texts.std, args );

@@ -149,3 +169,3 @@ }

parts.push( text.substring( start ) );
let remain = Object.keys( params ).filter( n => n && params[n] );
let remain = Object.keys( params ).filter( n => n !== '#' && params[n] );
return (remain.length)

@@ -152,0 +172,0 @@ ? parts.join('') + '; ' +

@@ -221,2 +221,33 @@ 'use strict';

// Check that min and max cardinalities of 'elem' in 'art' have legal values
function checkCardinality(elem, model) {
const { error, signal } = alerts(model);
if (!elem.cardinality) {
return;
}
// Max cardinalities must be a positive number or '*'
for (let prop of ['sourceMax', 'targetMax']) {
if (elem.cardinality[prop]) {
if (!(elem.cardinality[prop].literal == 'number' && elem.cardinality[prop].val > 0
|| elem.cardinality[prop].literal == 'string' && elem.cardinality[prop].val == '*')) {
signal(error`Element "${elem.name.absolute}.${elem.name.id}": Illegal value "${elem.cardinality[prop].val}" for max cardinality (must a positive number or "*")`, elem.cardinality[prop].location);
}
}
}
// Min cardinality must be a non-negative number (already checked by parser)
if (elem.cardinality.targetMin) {
if (!(elem.cardinality.targetMin.literal == 'number' && elem.cardinality.targetMin.val >= 0)) {
signal(error`Element "${elem.name.absolute}.${elem.name.id}": Illegal value "${elem.cardinality.targetMin.val}" for min cardinality (must a non-negative number)`, elem.cardinality.targetMin.location);
}
}
// If provided, min cardinality must not exceed max cardinality (note that '*' is considered to be >= any number)
if (elem.cardinality.targetMin && elem.cardinality.targetMax && elem.cardinality.targetMax.literal == 'number'
&& elem.cardinality.targetMin.val > elem.cardinality.targetMax.val) {
signal(error`Element "${elem.name.absolute}.${elem.name.id}": Target minimum cardinality must not be greater than target maximum cardinality`, elem.cardinality.location);
}
}
module.exports = {

@@ -226,2 +257,3 @@ checkManagedAssoc,

checkTypeParameters,
checkCardinality,
};

@@ -10,3 +10,3 @@ 'use strict';

const { checkArtifactImplementedIn, checkNotEmptyOrOnlyVirtualElems, checkNoUnmanagedAssocsInGroupByOrderBy } = require('./checkArtifacts');
const { checkVirtualElement, checkManagedAssoc } = require('./checkElements');
const { checkVirtualElement, checkManagedAssoc, checkCardinality } = require('./checkElements');

@@ -167,2 +167,3 @@ // Note: For the organization of these checks, we distinguish the following terms:

checkManagedAssoc(elem, model);
checkCardinality(elem, model);
}

@@ -169,0 +170,0 @@

@@ -25,3 +25,2 @@ // Consistency checker on model (XSN = augmented CSN)

'artifacts','namespace','usings', // CDL parser
'package', // HANA CDS
'filename', 'dirname', // TODO: move filename into a normal location?

@@ -31,2 +30,3 @@ 'dependencies', // for USING..FROM

'version', // TODO: do not set in parser
'@sql_mapping', // TODO: it is time that a 'header' attribute replaces 'version'
],

@@ -44,2 +44,3 @@ },

extensions: {
kind: ['context'], // syntax error (as opposed to HANA CDS), but still there
inherits: 'definitions',

@@ -99,3 +100,3 @@ test: isVector( standard ),

// TODO: rename namedArgs to args
optional: ['quoted','namedArgs','where','cardinality','_artifact','_navigation']
optional: ['quoted','args','namedArgs','where','cardinality','_artifact','_navigation']
},

@@ -150,3 +151,2 @@ id: { test: string },

dbType: { kind: true, test: () => true },
temporary: { kind: true, test: () => true }, // TODO: HANA-CDS - keep?
source: { kind: true, test: () => true }, // TODO: remove in JSON/CDL parser

@@ -369,9 +369,2 @@ projection: { kind: true, test: () => true }, // TODO: remove in JSON/CDL parser

function assertConsistency2(model, stage) {
let sql_mapping = model["@sql_mapping"];
delete model["@sql_mapping"]
assertConsistency(model, stage)
model["@sql_mapping"]=sql_mapping
}
module.exports = assertConsistency2;
module.exports = assertConsistency;

@@ -195,2 +195,4 @@ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN

let absolute;
if (path.broken)
return parent;
for (let item of path) {

@@ -407,4 +409,7 @@ let id = item.id;

// check checks whether union can be used)?
if (art._leadingQuery)
if (art._leadingQuery) {
if (!art._leadingQuery.elements)
art._leadingQuery.elements = Object.create(null);
art.elements = art._leadingQuery.elements;
}
if (!options.hanaFlavor)

@@ -416,2 +421,4 @@ initDollarSelf( art ); // to allow extend projection with auto-mixin assoc, see #924

addToDefinitions( art, undefined, prefix, parent );
if (art.dbType && !options.hanaFlavor)
message( `TABLE TYPE is not supported yet`, art.dbType.location );
defineAnnotations( art, art, block );

@@ -506,5 +513,6 @@ initMembers( art, art, block );

addQuery();
let elements = query.elements || Object.create(null); // be robust: parse error in queryTerm
if (parents.length)
// It is ensured that query.elements is defined (see `queryTerm` in grammar)
addAlias( { elements: query.elements }, query );
addAlias( { elements }, query );
for (let tab of query.from)

@@ -518,3 +526,3 @@ initQueryExpression( art, tab, [query] );

kind: '$tableAlias', self: true,
elements: query.elements, // TODO: shouldn't we set type._artifact: query instead?
elements, // TODO: shouldn't we set type._artifact: query instead?
location: query.location };

@@ -537,3 +545,3 @@ }

if (parents.length)
addAlias( { elements: leading.elements }, leading );
addAlias( { elements: leading.elements || Object.create(null) }, leading );
}

@@ -540,0 +548,0 @@ // else: with parse error (`select from <EOF>`)

@@ -5,179 +5,7 @@ //

const { forEachDefinition, forEachMember, applyLinearly, setProp }
const { forEachDefinition, forEachMember, setProp }
= require( '../base/model');
var { setMemberParent, linkToOrigin, withAssociation } = require('./shared');
var { linkToOrigin, withAssociation } = require('./shared');
// const { refString } = require( '../base/messages')
function propagateLinearly( ...args ) {
return applyLinearly( 'propagated', ...args );
}
// the export function
function propagateAssignments( model, options = model.options || {} ) {
const typeProperties = {
type: null, // propagateType,
length: null,
precision: null,
scale: null,
items: propagateTypeProperties,
target: 0,
implicitForeignKeys: null,
source: null,
// default: null, // see propagateSingle()
// notNull: null, // see propagateSingle()
cardinality: null,
foreignKeys: propagateDictionary,
on: null, onCond: 0, // combine, copy when resolve in ON
elements: propagateDictionary,
enum: propagateDictionary,
localized: null,
virtual: null,
};
forEachDefinition( model, function( def ) {
// TODO: make exportAnnotations inspect the _finalType instead:
if (options.toI18n && options.toI18n.style == 'prop') {
propagateLinearly( def, o => o.type, propagateTypeProperties );
return;
}
if (options.tntFlavor) {
if (!options.tntFlavor.skipPropagatingFromInclude)
propagateLinearly( def, includesSource, propagateSingle );
if (!options.tntFlavor.skipPropagatingFromProjectionSrc)
propagateLinearly( def, p => p.source, propagateSingle );
}
propagateInMembers( def );
});
return model;
function propagateInMembers( def ) {
forEachMember( def, function( elem ) {
propagateLinearly( elem, o => o.origin, propagateSingle );
propagateInMembers( elem );
});
}
// Propagation function. Used to inherit along properties `origin` (used for
// members only), and with --tnt-flavor: `source` and `includes[0]`. The
// properties are the type properties and annotation assignments.
function propagateSingle( target, source ) {
if (target.kind === 'key')
return;
propagateTypeProperties( target, source );
if (target.origin && source === target.origin._artifact) { // along `origin`
// inherit `default` only along structure includes
if ('default' in source && !('default' in target)) { //&&
// !target.value) { // from structure includes
target.default = source.default;
}
// inherit `notNull` along select items without association or structure includes
if ('notNull' in source && !('notNull' in target) &&
(!target.value || withoutAssociation( target.value.path )) ) {
target.notNull = source.notNull;
}
}
if (source.params) {
target.params = propagateDictionary( target.params, source.params, target );
}
if (source.returns)
target.returns = propagateTypeProperties( null, source.returns, target );
if (options.tntFlavor) {
if (!options.tntFlavor.skipPropagatingActions)
propagateActions( target, source );
// FIXME: very questionable if we really want that
if (source.includes && !options.tntFlavor.skipPropagatingIncludes) {
target.includes = source.includes;
}
}
for (let prop in source) {
if (prop.charAt(0) !== '@' ||
prop in target ||
options.tntFlavor && !options.tntFlavor.skipNotPropagatingIndexableAnno && prop === '@com.sap.gtt.core.CoreModel.Indexable')
continue;
// TODO: if we decide that annotations have a link to the attached
// construct, we need to shallow-copy and set this link here, too
target[prop] = source[prop];
// FIXME: the following makes toI18n fail! Why?
// target[prop] = Object.assign( { $inferred: 'prop' }, source[prop] );
}
}
function withoutAssociation( path ) {
for (let item of path || []) {
if (item._artifact && item._artifact._finalType && item._artifact._finalType.target)
return false;
}
return true;
}
//function propagateType( target, source )
function propagateTypeProperties( target, source, parent ) {
if (!target) {
target = Object.assign( {}, source );
// Object.defineProperty( target, '_finalType', { value: source._finalType } );
}
if (target.kind === 'key' && // TODO: this should move below !!!!!!!
( source.target || source.type && source.type._artifact && !source.type._artifact.builtin ))
return target; // do not propagate for foreign keys which are assocs - TODO
let value = (target.redirected) ? target : (source._finalType || source);
// Propagated `elements`, `foreignKeys` etc should have a _finalType:
if (!target._finalType)
setProp( target, '_finalType', value );
// TODO: adapt foreign keys with redirect and on condition in general
if (!parent && !target.redirected) {
for (let prop in typeProperties) {
if (prop in target && typeProperties[prop] !== 0) {
setProp( target, '_finalType', target );
return target;
}
}
}
for (let prop in typeProperties) {
// if (prop === 'foreignKeys') console.log ('FK', source.kind, source.name)
if (prop in source && !(prop in target)) {
let transfer = typeProperties[prop];
target[prop] = (transfer)
? transfer( null, source[prop], parent || target )
: source[prop];
}
}
return target;
}
function propagateDictionary( target, source, parent ) {
if (!target)
target = Object.create(null);
for (let name in source) {
if (name in target)
continue;
let src = source[name];
let elem = target[name] = Object.assign( {}, src );
elem.name = Object.assign( {}, src.name );
// console.log(elem.kind,elem.name)
setMemberParent( elem, name, parent );
if (src.returns) // TODO: what about items?
elem.returns = propagateTypeProperties( null, src.returns, elem );
else
propagateTypeProperties( elem, src, elem );
if (src.params) // for actions
elem.params = propagateDictionary( null, src.params, elem );
}
return target;
}
function propagateActions( target, source ) {
if (source.actions) {
target.actions = propagateDictionary( target.actions, source.actions, target );
}
}
// If we have more than one include later, we need something like applyInOrder
function includesSource( def ) {
return def.includes && def.includes.length >= 1 && def.includes[0];
}
}
function propagate( model ) {

@@ -188,3 +16,3 @@ const props = {

'@cds.persistence.table': never,
'@': always,
'@': annotation, // always except in 'returns' and 'items'
default: always,

@@ -223,4 +51,4 @@ virtual: notViaType,

if (!checkAndSetStatus( art )) {
// console.log('DONE:',refString(art), art.elements ? Object.keys(art.elements) : 0)
forEachMember( art, run );
if ( art.status !== 'propagated')
runMembers( art );
return;

@@ -237,21 +65,41 @@ }

}
if (source) { // the source has fully propagated properties
if (source) { // the source has fully propagated properties
step({ target, source });
}
else if (target._main) { // source is element, which has not inherited props yet
run( target._main ) // run on main artifact first
}
else if (target.includes) {
let news = [ target ];
while (news.length) {
let structs = [].concat( ...news );
news = [];
for (target of structs) {
let incl = target.includes;
if (incl) {
chain.push( ...incl.map( i => ({ target, source: i._artifact }) ) );
news.push( incl.map( i => i._artifact ).filter( checkAndSetStatus ) );
let targets = [ target ];
while (targets.length) {
let news = [];
for (let t of targets) {
for (let ref of t.includes || []) {
let s = ref._artifact;
if (!s) // ref error
continue;
chain.push( { target: t, source: s } );
if (checkAndSetStatus( s ))
news.push( s );
}
}
targets = news;
}
}
chain.reverse().forEach( step );
runMembers( art );
// console.log('DONE:',refString(art), art.elements ? Object.keys(art.elements) : 0)
}
function runMembers( art ) {
// console.log('MEMBERS:',refString(art), art.elements ? Object.keys(art.elements) : 0)
forEachMember( art, run ); // after propagation in parent!
let obj = art;
if (art.returns) {
obj = art.returns;
run( obj );
}
if (obj.items)
run( obj.items );
setProp( art, '_status', 'propagated' );
}

@@ -270,2 +118,3 @@

}
// propagate NOT NULL and VIRTUAL from sub elements:
if (target.$inferred !== 'proxy' &&

@@ -283,2 +132,3 @@ target.kind === 'element' && source.kind === 'element') {

}
//setProp( target, '_status', 'shallow-propagated' );
}

@@ -292,4 +142,2 @@

setProp( target[prop], '_artifact', source[prop]._artifact );
if ('_artifact' in source[prop])
setProp( target[prop], '_artifact', source[prop]._artifact );
}

@@ -326,2 +174,7 @@

function annotation( prop, target, source ) {
if (!target._outer) // not in 'returns' and 'items'
always( prop, target, source );
}
function notNull( prop, target, source, viaType ) {

@@ -335,8 +188,7 @@ // TODO: also if we access sub element - set in definer/resolver

// TODO: remove returns in XSN
if (ok || target.$inferred === 'proxy') {
if (ok || target.$inferred === 'proxy' || target.$inferred === 'include' ) {
let origin = {};
target[prop] = { $inferred: 'proxy', origin };
setProp( origin, '_artifact', source[prop] );
setProp( target[prop], '_outer', target._outer || target );
run( target[prop] );
setProp( target[prop], '_outer', target._outer || target ); // for setMemberParent
}

@@ -371,5 +223,5 @@ }

function checkAndSetStatus( art ) {
if (art._status === 'propagated')
if (art._status === 'propagated' || art._status === 'propagating')
return false;
setProp( art, '_status', 'propagated' );
setProp( art, '_status', 'propagating' );
return true;

@@ -380,3 +232,2 @@ }

propagate,
propagateAssignments
};

@@ -190,2 +190,6 @@ // Compiler phase "resolve": resolve all references

return;
if (!options.hanaFlavor && !options.betaMode && tc.location) {
message( null, tc.location, {}, 'Error',
'TECHNICAL CONFIGURATION is not supported yet' );
}

@@ -435,3 +439,3 @@ // secondary and fulltext indexes

message( 'anno-duplicate', a.name.location,
{ anno: annoName, '': justOnePerLayer && 'unrelated' },
{ anno: annoName, '#': justOnePerLayer && 'unrelated' },
['Error'], {

@@ -637,4 +641,3 @@ std: 'Duplicate assignment with $(ANNO)',

// TODO: name resolution for types etc in MIXIN definitions of subqueries?
if (options.betaMode || options.newCsn)
forEachGeneric( { elements: query.elements }, 'elements', resolveRedirected );
forEachGeneric( { elements: query.elements }, 'elements', resolveRedirected );
forEachGeneric( { elements: query.elements }, 'elements', resolveElem );

@@ -661,2 +664,4 @@ if (dictOB)

let from = leading.from[0]; // TODO: what about `union` - allow currently
if (!from) // parse error SELECT FROM <EOF>
return false;
if (from.join)

@@ -744,8 +749,12 @@ return false;

while (target.query) {
let from = target.query.from;
let from = (target.query.args) ? [1,2] : target.query.from;
if (!from || !from.length) // parse error
return;
if (from.length > 1 || !from[0].path) {
message( 'redirected-to-complex', elem.target.location, { art: target },
'Warning', 'The redirected target $(ART) is a complex view' );
message( 'redirected-to-complex', elem.target.location,
{ art: target, '#': target === elem.target._artifact ? 'target' : 'std' },
'Warning', {
std: 'Redirection involves the complex view $(ART)',
target: 'The redirected target $(ART) is a complex view'
});
break;

@@ -787,10 +796,11 @@ }

if (!art.query) // yes, no else if
let query = art._leadingQuery;
if (!query)
return false;
else if (!art.query.$tableAliases) // something wrong with query
return true; // -> no message about redirected target
else if (!query.$tableAliases) // something wrong with query
return true; // -> no message about redirected target
// With Node 7, we could do: Object.values(…).filter(…).map(…)
let sources = [];
for (let n in art.query.$tableAliases) {
let a = art.query.$tableAliases[n];
for (let n in query.$tableAliases) {
let a = query.$tableAliases[n];
if (a.type && !a.self && !a.name.$mixin)

@@ -1004,11 +1014,17 @@ sources.push( a.type._artifact );

if (!alias.$navigation) {
let tab = resolvePath( alias.type, 'from', query._main._block, query._main );
let main = query._main;
// if (main._block.$frontend!=='json') console.log('TABREF:',alias.name,main,main._block)
let tab = resolvePath( alias.type, 'from', main, main._block );
for (let step of tab && alias.type.path || []) {
if (!step._artifact)
break;
if (step.namedArgs)
resolveParams( step.namedArgs, step._artifact, query._main._block ); // block for future :const
if (step.namedArgs) {
if (!options.betaMode && !options.hanaFlavor)
message( null, dictLocation( step.namedArgs ), {},
'Error', 'View arguments are not supported yet' );
resolveParams( step.namedArgs, step._artifact, main._block ); // block for future :const
}
if (step.where) // TODO: support for $projection, $parameters, ...?
resolveExpr( step.where, 'element', null, {},
environment( step._artifact ), query._main._block );
environment( step._artifact ), main._block );
}

@@ -1015,0 +1031,0 @@ let isPrimaryAlias = alias === alias._parent._firstAliasInFrom;

@@ -143,2 +143,4 @@ // Compiler functions and utilities shared across all phases

art = model.definitions[ art.name.absolute ];
if (art instanceof Array) // redefined art referenced by using proxy
art = undefined;
}

@@ -217,2 +219,3 @@ else if (art && art.name.$mixin) {

function getPathRoot( path, spec, env, extDict ) {
// console.log(pathName(path), !spec.next && !extDict && (spec.useDefinitions || env.$frontend === 'json' || env))
if (!spec.next && !extDict)

@@ -316,2 +319,6 @@ extDict = (spec.useDefinitions || ['json','i18n','xml'].includes(env.$frontend))

}
// else if (!art.name) { we could add this to be more robust
// signalNotFound( error`Element ${msgName( item.id )} has not been found}`,
// item.location, [env], spec.severity );
// }
else if (art.name.alias && art.type && art.type._artifact)

@@ -437,3 +444,3 @@ {

function pathName (path) {
return path.map( id => id.id ).join('.');
return (path.broken) ? '' : path.map( id => id.id ).join('.');
}

@@ -440,0 +447,0 @@

@@ -255,4 +255,6 @@ 'use strict';

Bool : val => val == "true",
Byte : val => parseInt(val),
Int : val => parseInt(val),
Decimal : val => parseFloat(val),
Float : val => parseFloat(val),
EnumMember : val => handleEnumValue(val),

@@ -259,0 +261,0 @@ Path : val => ({ "=": val.replace(/\//g, '.') }),

@@ -145,2 +145,8 @@ const parseXml = require('./xmlParserWithLocations');

},
Float: val => {
return {
literal: 'decimal',
val: Number(val)
}
},
Int: val => {

@@ -147,0 +153,0 @@ return {

@@ -37,2 +37,4 @@ const edmxDict = require('./vocabularies/Dictionary.json');

case 'Edm.Double':
checkIfProp('Float');
break;
case 'Edm.Decimal':

@@ -39,0 +41,0 @@ checkIfProp('Decimal');

@@ -13,6 +13,5 @@ 'use strict';

* options:
* v
* v - array with two boolean entries, first is for v2, second is for v4
* tntFlavor
* embeddedV2
* suppressMessages --- currently not used
*/

@@ -36,3 +35,7 @@ function csn2annotationEdm(csn, options=undefined) {

// TODO only works for single service
// Note: only works for single service
// Note: we assume that all objects ly flat in the service, i.e. objName always
// looks like <service name, can contain dots>.<id>
// Note: it is NOT safe to assume that the service itself is the first definition in the csn
// -> in general "serviceName" is NOT correctly set during processing of other objects
let serviceName = null;

@@ -45,8 +48,13 @@ for (let objName in csn.definitions) {

// handle the annotations directly tied to the object
handleAnnotations(objName, object);
// handle the annotations of the object's elements
handleElements(objName, object);
// handle the annotations of the object's actions
handleActions(objName, object);
if (object.kind == "action" || object.kind == "function") {
handleAction(objName, object, null);
}
else { // service, entity, anything else?
// handle the annotations directly tied to the object
handleAnnotations(objName, object);
// handle the annotations of the object's elements
handleElements(objName, object);
// handle the annotations of the object's actions
handleBoundActions(objName, object);
}
}

@@ -59,2 +67,9 @@

// helper to determine the OData version
// tnt is always v2
// TODO: improve option handling and revoce this hack for tnt
function isV2() {
return options.tntFlavor || (v && v[0]);
}
//-------------------------------------------------------------------------------------------------

@@ -132,28 +147,60 @@ //-------------------------------------------------------------------------------------------------

// handle the annotations of actions and action parameters
// in: cObjectname : name of the object that holds the actions
// cObject : the object itself
function handleActions(cObjectname, cObject) {
// edm target name is "<serviceName>.EntityContainer/<actionName>" or
// "<serviceName>.EntityContainer/<actionName>/<parameterName>"
// TODO the respective entity names do not appear in the target name
// => in edm all actions are in the same namespace
// => what if two or more entities have actions with same names?
// annotations for actions and functions (and their parameters)
// v2, unbound: Target = <service>.EntityContainer/<action/function>
// v2, bound: Target = <service>.EntityContainer/<entity>_<action/function>
// v4, unbound action: Target = <service>.<action>()
// v4, bound action: Target = <service>.<action>(<service>.<entity>)
// v4, unbound function: Target = <service>.<function>(<1st param type>, <2nd param type>, ...)
// v4, bound function: Target = <service>.<function>(<service>.<entity>, <1st param type>, <2nd param type>, ...)
// service name -> remove last part of the name
// TODO this only works if all objects ly flat in the service
// handle the annotations of cObject's (an entity) bound actions/functions and their parameters
// in: cObjectname : qualified name of the object that holds the actions
// cObject : the object itself
function handleBoundActions(cObjectname, cObject) {
// service name -> remove last part of the object name
// only works if all objects ly flat in the service
let nameParts = cObjectname.split(".")
nameParts.pop();
let entityName = nameParts.pop();
let serviceName = nameParts.join(".");
for (let actionName in cObject.actions) {
let action = cObject.actions[actionName];
let edmTargetName = serviceName + ".EntityContainer/" + actionName;
handleAnnotations(edmTargetName, action);
for (let paramName in cObject.actions[actionName].params) {
let param = cObject.actions[actionName].params[paramName];
edmTargetName = serviceName + ".EntityContainer/" + actionName + "/" + paramName;
handleAnnotations(edmTargetName, param);
for (let n in cObject.actions) {
let action = cObject.actions[n];
let actionName = serviceName + "." + (isV2() ? entityName + '_' : '') + n;
handleAction(actionName, action, cObjectname);
}
}
// handle the annotations of an action and its parameters
// called by handleBoundActions and directly for unbound actions/functions
// in: cActionName : qualified name of the action
// cAction : the action object
// entityNameIfBound : qualified name of entity if bound action/function
function handleAction(cActionName, cAction, entityNameIfBound) {
let actionName = cActionName;
if (isV2()) { // insert before last "."
actionName = actionName.replace(/\.(?=[^.]*$)/, '.EntityContainer/')
}
else { // add parameter type list
actionName += relParList(cAction, entityNameIfBound);
}
handleAnnotations(actionName, cAction);
for (let n in cAction.params) { // handle parameters
let edmTargetName = actionName + "/" + n;
handleAnnotations(edmTargetName, cAction.params[n]);
}
}
function relParList(action, bindingParam) {
// we rely on the order of params in the csn being the correct one
let params = [];
if (bindingParam) params.push(bindingParam);
if (action.kind === 'function') {
for (let n in action.params) {
let p = action.params[n];
let otype = p.type.startsWith('cds.') ? glue.mapCdsToEdmType(p.type, false /*is only called for v4*/) : p.type;
params.push(otype);
}
}
return '(' + params.join(',') + ')';
}

@@ -388,2 +435,7 @@

// expected type is dTypeName
// mappping rule for values:
// if expected type is ... the expression to be generated is ...
// floating point type except Edm.Decimal -> Float
// Edm.Decimal -> Decimal
// integer tpye -> Int
function handleSimpleValue(val, dTypeName, context) {

@@ -413,3 +465,3 @@ // caller already made sure that val is neither object nor array

}
else if (dTypeName == "Edm.Double") {
else if (dTypeName == "Edm.Decimal") {
if (isNaN(val) || isNaN(parseFloat(val))) {

@@ -422,2 +474,10 @@ errorMessage(context, "found non-numeric string, but expected type " + dTypeName);

}
else if (dTypeName == "Edm.Double") {
if (isNaN(val) || isNaN(parseFloat(val))) {
errorMessage(context, "found non-numeric string, but expected type " + dTypeName);
}
else {
typeName = "Float";
}
}
else if (isComplexType(dTypeName)) {

@@ -456,10 +516,13 @@ errorMessage(context, "found String, but expected complex type " + dTypeName);

}
else if (dTypeName == "Edm.Double") {
typeName = "Decimal";
}
else if (dTypeName == "Edm.Boolean") {
errorMessage(context, "found number, but expected type " + dTypeName);
}
else if (dTypeName == "Edm.Decimal") {
typeName = "Decimal";
}
else if (dTypeName == "Edm.Double") {
typeName = "Float";
}
else {
typeName = Number.isInteger(val) ? 'Int' : 'Decimal';
typeName = Number.isInteger(val) ? 'Int' : 'Float';
}

@@ -466,0 +529,0 @@ }

@@ -31,3 +31,8 @@ 'use strict';

// helper function
// helper functions
function getTargetOfAssoc(assoc) {
// assoc.target can be the name of the target or the object itself
return (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
}
function getKeyOfTargetOfManagedAssoc(assoc) {

@@ -160,8 +165,16 @@ // assoc.target can be the name of the target or the object itself

let path = ae["="];
if (carrier.kind == 'entity') {
let element = carrier.elements[path];
// FIXME: Deal with multi-step paths properly
if (element && glue.isAssociation(element) && !element.onCond) {
ae["="] = path + fkSeparator + getKeyOfTargetOfManagedAssoc(element);
let steps = path.split('.');
let ent = carrier;
for (let i in steps) {
if (!ent || ent.kind != 'entity') return;
let el = ent.elements[steps[i]];
if (el && glue.isAssociation(el)) {
if (i < steps.length-1) {
ent = getTargetOfAssoc(el);
}
else { //last step
if (!el.onCond) { // only for managed
ae["="] += fkSeparator + getKeyOfTargetOfManagedAssoc(el);
}
}
}

@@ -326,9 +339,8 @@ }

if (options && options.tntFlavor && !options.tntFlavor.skipAnnosSubstitutingFKeysForAssocs) {
// replace association by fk field
if (aNameWithoutQualifier == "@UI.LineItem" ||
aNameWithoutQualifier == "@UI.Identification" ||
aNameWithoutQualifier == "@UI.FieldGroup") {
for (let i in a) {
let ae = a[i];
if (ae["Value"] != undefined && ae["Value"]["="] != undefined) {
// replace association by fk field, mind nested annotatinos
if (aNameWithoutQualifier == "@UI.LineItem" || aNameWithoutQualifier == "@UI.LineItem.$value" ||
aNameWithoutQualifier == "@UI.Identification" || aNameWithoutQualifier == "@UI.Identification.$value" ||
aNameWithoutQualifier == "@UI.FieldGroup" || aNameWithoutQualifier == "@UI.FieldGroup.$value") {
for (let ae of a) {
if (ae["Value"] && ae["Value"]["="]) {
replaceManagedAssocByFK(ae["Value"]);

@@ -341,4 +353,3 @@ }

if (aNameWithoutQualifier == "@UI.SelectionFields") {
for (let i in a) {
let ae = a[i];
for (let ae of a) {
if ("=" in ae) {

@@ -345,0 +356,0 @@ replaceManagedAssocByFK(ae);

@@ -176,3 +176,3 @@ 'use strict';

}
else // unbound
else // unbound => produce Action/FunctionImport
{

@@ -202,5 +202,5 @@ let actionImport = iAmAnAction ? Edm.ActionImport.create(v, { Name: actionName, Action : fullQualified(actionName) })

{
actionNode._rtNode = Edm.ReturnType.create(v, actionCsn.returns, fullQualified);
actionNode._returnType = Edm.ReturnType.create(v, actionCsn.returns, fullQualified);
}
Schema.append(actionNode);
Schema.addAction(actionNode);
}

@@ -247,5 +247,5 @@

// Make bound function names always unique as per Ralf's recommendation
functionImport.setXml( {'sap:action-for': fullQualified(entityCsn.name) } );
functionImport.Name = entityCsn.name.replace(namespace, '') + '_' + functionImport.Name;
functionImport.setXml( {'sap:action-for': fullQualified(entityCsn.name) } );
// Binding Parameter: Primary Keys at first position in sequence, this is decisive!

@@ -510,10 +510,6 @@ glue.foreach(entityCsn.elements,

}
function createAnnotations(edm)
{
let annoEdm;
if(options && options.tntFlavor)
{
options.suppressMessages = true;
}
annoEdm = translate.csn2annotationEdm(model, options);

@@ -520,0 +516,0 @@

@@ -209,3 +209,3 @@ 'use strict'

let schema = super.create(v, props);
schema.set( { _annotations: annotations } );
schema.set( { _annotations: annotations, _actions: {} } );
schema.setXml( { xmlns: (schema.v2) ? "http://schemas.microsoft.com/ado/2008/09/edm" : "http://docs.oasis-open.org/odata/ns/edm" } );

@@ -227,2 +227,11 @@

// hold actions and functions in V4
addAction(action)
{
if(this._actions[action.Name])
this._actions[action.Name].push(action);
else
this._actions[action.Name] = [action];
}
setAnnotations(annotations)

@@ -240,2 +249,6 @@ {

xml += super.innerXML(indent);
glue.forAll(this._actions, actionArray => {
actionArray.forEach(action => {
xml += action.toXML(indent, what) + '\n'; });
});
}

@@ -258,3 +271,9 @@ if(what=='annotations' || what=='all')

this.toJSONchildren(json);
return json
glue.forAll(this._actions, (actionArray, actionName) => {
json[actionName] = [];
actionArray.forEach(action => {
json[actionName].push(action.toJSON());
});
});
return json;
}

@@ -527,9 +546,8 @@

overloaded XML and JSON rendering of parameters and
return type
The non-enumberable properties _paramNodes and _rtNode
store the parmater list and the eventually existing
ReturnType node in V4. In V2 the return type is a
direct attribute called ReturnType to the FunctionImport.
See comment on class FunctionImport
return type. Parameters are _children.
_returnType holds the eventually existing ReturnType in V4.
In V2 the return type is a direct attribute called ReturnType
to the FunctionImport. See comment in class FunctionImport.
*/
class ActionFunctionBase extends Node

@@ -541,3 +559,3 @@ {

let node = super.create(v, details);
node.set( { _paramNodes: [], _rtNode: undefined });
node.set( { _returnType: undefined });
return node;

@@ -549,15 +567,7 @@ }

let xml = super.innerXML(indent);
if(this._rtNode != undefined)
xml += this._rtNode.toXML(indent) + '\n';
if(this._returnType != undefined)
xml += this._returnType.toXML(indent) + '\n';
return xml
}
// virtual
toJSON()
{
let json = [ super.toJSON() ];
return json;
}
toJSONchildren(json)

@@ -569,5 +579,5 @@ {

json['$Parameter'] = json_parameters;
if(this._rtNode)
if(this._returnType)
{
json['$ReturnType'] = this._rtNode.toJSON();
json['$ReturnType'] = this._returnType.toJSON();
}

@@ -590,3 +600,3 @@ return json;

*/
class FunctionImport extends ActionFunctionBase {}
class FunctionImport extends Node {} //ActionFunctionBase {}
class ActionImport extends Node {}

@@ -593,0 +603,0 @@

@@ -1,2 +0,2 @@

// Generated from JSON.g4 by ANTLR 4.6
// Generated from JSON.g4 by ANTLR 4.7.1
// jshint ignore: start

@@ -34,3 +34,3 @@ var antlr4 = require('antlr4/index');

var serializedATN = ["\u0003\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd",
var serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964",
"\u0002\u0010\u00a5\b\u0001\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004",

@@ -66,38 +66,38 @@ "\u0004\t\u0004\u0004\u0005\t\u0005\u0004\u0006\t\u0006\u0004\u0007\t",

"3;\u0004\u0002GGgg\u0004\u0002--//\u0005\u0002\u000b\f\u000f\u000f\"",
"\"\u0004\u0002\f\f\u000f\u000f\u00af\u0002\u0003\u0003\u0002\u0002\u0002",
"\u0002\u0005\u0003\u0002\u0002\u0002\u0002\u0007\u0003\u0002\u0002\u0002",
"\u0002\t\u0003\u0002\u0002\u0002\u0002\u000b\u0003\u0002\u0002\u0002",
"\u0002\r\u0003\u0002\u0002\u0002\u0002\u000f\u0003\u0002\u0002\u0002",
"\u0002\u0011\u0003\u0002\u0002\u0002\u0002\u0013\u0003\u0002\u0002\u0002",
"\u0002\u0015\u0003\u0002\u0002\u0002\u0002\u001d\u0003\u0002\u0002\u0002",
"\u0002#\u0003\u0002\u0002\u0002\u0002%\u0003\u0002\u0002\u0002\u0002",
"\'\u0003\u0002\u0002\u0002\u0003)\u0003\u0002\u0002\u0002\u0005+\u0003",
"\u0002\u0002\u0002\u0007-\u0003\u0002\u0002\u0002\t/\u0003\u0002\u0002",
"\u0002\u000b1\u0003\u0002\u0002\u0002\r3\u0003\u0002\u0002\u0002\u000f",
"5\u0003\u0002\u0002\u0002\u0011:\u0003\u0002\u0002\u0002\u0013@\u0003",
"\u0002\u0002\u0002\u0015E\u0003\u0002\u0002\u0002\u0017O\u0003\u0002",
"\u0002\u0002\u0019T\u0003\u0002\u0002\u0002\u001bZ\u0003\u0002\u0002",
"\u0002\u001ds\u0003\u0002\u0002\u0002\u001f}\u0003\u0002\u0002\u0002",
"!\u007f\u0003\u0002\u0002\u0002#\u0086\u0003\u0002\u0002\u0002%\u008c",
"\u0003\u0002\u0002\u0002\'\u009a\u0003\u0002\u0002\u0002)*\u0007}\u0002",
"\u0002*\u0004\u0003\u0002\u0002\u0002+,\u0007.\u0002\u0002,\u0006\u0003",
"\u0002\u0002\u0002-.\u0007\u007f\u0002\u0002.\b\u0003\u0002\u0002\u0002",
"/0\u0007<\u0002\u00020\n\u0003\u0002\u0002\u000212\u0007]\u0002\u0002",
"2\f\u0003\u0002\u0002\u000234\u0007_\u0002\u00024\u000e\u0003\u0002",
"\u0002\u000256\u0007v\u0002\u000267\u0007t\u0002\u000278\u0007w\u0002",
"\u000289\u0007g\u0002\u00029\u0010\u0003\u0002\u0002\u0002:;\u0007h",
"\u0002\u0002;<\u0007c\u0002\u0002<=\u0007n\u0002\u0002=>\u0007u\u0002",
"\u0002>?\u0007g\u0002\u0002?\u0012\u0003\u0002\u0002\u0002@A\u0007p",
"\u0002\u0002AB\u0007w\u0002\u0002BC\u0007n\u0002\u0002CD\u0007n\u0002",
"\u0002D\u0014\u0003\u0002\u0002\u0002EJ\u0007$\u0002\u0002FI\u0005\u0017",
"\f\u0002GI\n\u0002\u0002\u0002HF\u0003\u0002\u0002\u0002HG\u0003\u0002",
"\u0002\u0002IL\u0003\u0002\u0002\u0002JH\u0003\u0002\u0002\u0002JK\u0003",
"\u0002\u0002\u0002KM\u0003\u0002\u0002\u0002LJ\u0003\u0002\u0002\u0002",
"MN\u0007$\u0002\u0002N\u0016\u0003\u0002\u0002\u0002OR\u0007^\u0002",
"\u0002PS\t\u0003\u0002\u0002QS\u0005\u0019\r\u0002RP\u0003\u0002\u0002",
"\u0002RQ\u0003\u0002\u0002\u0002S\u0018\u0003\u0002\u0002\u0002TU\u0007",
"w\u0002\u0002UV\u0005\u001b\u000e\u0002VW\u0005\u001b\u000e\u0002WX",
"\u0005\u001b\u000e\u0002XY\u0005\u001b\u000e\u0002Y\u001a\u0003\u0002",
"\u0002\u0002Z[\t\u0004\u0002\u0002[\u001c\u0003\u0002\u0002\u0002\\",
"^\u0007/\u0002\u0002]\\\u0003\u0002\u0002\u0002]^\u0003\u0002\u0002",
"\"\u0004\u0002\f\f\u000f\u000f\u0002\u00af\u0002\u0003\u0003\u0002\u0002",
"\u0002\u0002\u0005\u0003\u0002\u0002\u0002\u0002\u0007\u0003\u0002\u0002",
"\u0002\u0002\t\u0003\u0002\u0002\u0002\u0002\u000b\u0003\u0002\u0002",
"\u0002\u0002\r\u0003\u0002\u0002\u0002\u0002\u000f\u0003\u0002\u0002",
"\u0002\u0002\u0011\u0003\u0002\u0002\u0002\u0002\u0013\u0003\u0002\u0002",
"\u0002\u0002\u0015\u0003\u0002\u0002\u0002\u0002\u001d\u0003\u0002\u0002",
"\u0002\u0002#\u0003\u0002\u0002\u0002\u0002%\u0003\u0002\u0002\u0002",
"\u0002\'\u0003\u0002\u0002\u0002\u0003)\u0003\u0002\u0002\u0002\u0005",
"+\u0003\u0002\u0002\u0002\u0007-\u0003\u0002\u0002\u0002\t/\u0003\u0002",
"\u0002\u0002\u000b1\u0003\u0002\u0002\u0002\r3\u0003\u0002\u0002\u0002",
"\u000f5\u0003\u0002\u0002\u0002\u0011:\u0003\u0002\u0002\u0002\u0013",
"@\u0003\u0002\u0002\u0002\u0015E\u0003\u0002\u0002\u0002\u0017O\u0003",
"\u0002\u0002\u0002\u0019T\u0003\u0002\u0002\u0002\u001bZ\u0003\u0002",
"\u0002\u0002\u001ds\u0003\u0002\u0002\u0002\u001f}\u0003\u0002\u0002",
"\u0002!\u007f\u0003\u0002\u0002\u0002#\u0086\u0003\u0002\u0002\u0002",
"%\u008c\u0003\u0002\u0002\u0002\'\u009a\u0003\u0002\u0002\u0002)*\u0007",
"}\u0002\u0002*\u0004\u0003\u0002\u0002\u0002+,\u0007.\u0002\u0002,\u0006",
"\u0003\u0002\u0002\u0002-.\u0007\u007f\u0002\u0002.\b\u0003\u0002\u0002",
"\u0002/0\u0007<\u0002\u00020\n\u0003\u0002\u0002\u000212\u0007]\u0002",
"\u00022\f\u0003\u0002\u0002\u000234\u0007_\u0002\u00024\u000e\u0003",
"\u0002\u0002\u000256\u0007v\u0002\u000267\u0007t\u0002\u000278\u0007",
"w\u0002\u000289\u0007g\u0002\u00029\u0010\u0003\u0002\u0002\u0002:;",
"\u0007h\u0002\u0002;<\u0007c\u0002\u0002<=\u0007n\u0002\u0002=>\u0007",
"u\u0002\u0002>?\u0007g\u0002\u0002?\u0012\u0003\u0002\u0002\u0002@A",
"\u0007p\u0002\u0002AB\u0007w\u0002\u0002BC\u0007n\u0002\u0002CD\u0007",
"n\u0002\u0002D\u0014\u0003\u0002\u0002\u0002EJ\u0007$\u0002\u0002FI",
"\u0005\u0017\f\u0002GI\n\u0002\u0002\u0002HF\u0003\u0002\u0002\u0002",
"HG\u0003\u0002\u0002\u0002IL\u0003\u0002\u0002\u0002JH\u0003\u0002\u0002",
"\u0002JK\u0003\u0002\u0002\u0002KM\u0003\u0002\u0002\u0002LJ\u0003\u0002",
"\u0002\u0002MN\u0007$\u0002\u0002N\u0016\u0003\u0002\u0002\u0002OR\u0007",
"^\u0002\u0002PS\t\u0003\u0002\u0002QS\u0005\u0019\r\u0002RP\u0003\u0002",
"\u0002\u0002RQ\u0003\u0002\u0002\u0002S\u0018\u0003\u0002\u0002\u0002",
"TU\u0007w\u0002\u0002UV\u0005\u001b\u000e\u0002VW\u0005\u001b\u000e",
"\u0002WX\u0005\u001b\u000e\u0002XY\u0005\u001b\u000e\u0002Y\u001a\u0003",
"\u0002\u0002\u0002Z[\t\u0004\u0002\u0002[\u001c\u0003\u0002\u0002\u0002",
"\\^\u0007/\u0002\u0002]\\\u0003\u0002\u0002\u0002]^\u0003\u0002\u0002",
"\u0002^_\u0003\u0002\u0002\u0002_`\u0005\u001f\u0010\u0002`b\u00070",

@@ -155,2 +155,8 @@ "\u0002\u0002ac\t\u0005\u0002\u0002ba\u0003\u0002\u0002\u0002cd\u0003",

Object.defineProperty(JSONLexer.prototype, "atn", {
get : function() {
return atn;
}
});
JSONLexer.EOF = antlr4.Token.EOF;

@@ -172,2 +178,3 @@ JSONLexer.T__0 = 1;

JSONLexer.prototype.channelNames = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ];

@@ -174,0 +181,0 @@ JSONLexer.prototype.modeNames = [ "DEFAULT_MODE" ];

@@ -1,2 +0,2 @@

// Generated from JSON.g4 by ANTLR 4.6
// Generated from JSON.g4 by ANTLR 4.7.1
// jshint ignore: start

@@ -34,3 +34,3 @@ var antlr4 = require('antlr4/index');

var serializedATN = ["\u0003\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd",
var serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964",
"\u0003\u0010H\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004\u0004\t",

@@ -48,34 +48,34 @@ "\u0004\u0004\u0005\t\u0005\u0004\u0006\t\u0006\u0003\u0002\u0003\u0002",

"\u0006\u0003\u0006\u0003\u0006\u0005\u0006F\n\u0006\u0003\u0006\u0002",
"\u0002\u0007\u0002\u0004\u0006\b\n\u0002\u0002M\u0002\u000e\u0003\u0002",
"\u0002\u0002\u0004\u001d\u0003\u0002\u0002\u0002\u0006\u001f\u0003\u0002",
"\u0002\u0002\b7\u0003\u0002\u0002\u0002\nE\u0003\u0002\u0002\u0002\f",
"\u000f\u0005\u0004\u0003\u0002\r\u000f\u0005\b\u0005\u0002\u000e\f\u0003",
"\u0002\u0002\u0002\u000e\r\u0003\u0002\u0002\u0002\u000f\u0003\u0003",
"\u0002\u0002\u0002\u0010\u0011\u0007\u0003\u0002\u0002\u0011\u0016\u0005",
"\u0006\u0004\u0002\u0012\u0013\u0007\u0004\u0002\u0002\u0013\u0015\u0005",
"\u0006\u0004\u0002\u0014\u0012\u0003\u0002\u0002\u0002\u0015\u0018\u0003",
"\u0002\u0002\u0002\u0016\u0014\u0003\u0002\u0002\u0002\u0016\u0017\u0003",
"\u0002\u0002\u0002\u0017\u0019\u0003\u0002\u0002\u0002\u0018\u0016\u0003",
"\u0002\u0002\u0002\u0019\u001a\u0007\u0005\u0002\u0002\u001a\u001e\u0003",
"\u0002\u0002\u0002\u001b\u001c\u0007\u0003\u0002\u0002\u001c\u001e\u0007",
"\u0005\u0002\u0002\u001d\u0010\u0003\u0002\u0002\u0002\u001d\u001b\u0003",
"\u0002\u0002\u0002\u001e\u0005\u0003\u0002\u0002\u0002\u001f \u0007",
"\f\u0002\u0002 !\b\u0004\u0001\u0002!\"\u0007\u0006\u0002\u0002\"#\u0005",
"\n\u0006\u0002#$\b\u0004\u0001\u0002$\u0007\u0003\u0002\u0002\u0002",
"%&\u0007\u0007\u0002\u0002&\'\b\u0005\u0001\u0002\'(\u0005\n\u0006\u0002",
"(0\b\u0005\u0001\u0002)*\u0007\u0004\u0002\u0002*+\b\u0005\u0001\u0002",
"+,\u0005\n\u0006\u0002,-\b\u0005\u0001\u0002-/\u0003\u0002\u0002\u0002",
".)\u0003\u0002\u0002\u0002/2\u0003\u0002\u0002\u00020.\u0003\u0002\u0002",
"\u000201\u0003\u0002\u0002\u000213\u0003\u0002\u0002\u000220\u0003\u0002",
"\u0002\u000234\u0007\b\u0002\u000248\u0003\u0002\u0002\u000256\u0007",
"\u0007\u0002\u000268\u0007\b\u0002\u00027%\u0003\u0002\u0002\u00027",
"5\u0003\u0002\u0002\u00028\t\u0003\u0002\u0002\u00029:\u0007\f\u0002",
"\u0002:F\b\u0006\u0001\u0002;<\u0007\r\u0002\u0002<F\b\u0006\u0001\u0002",
"=F\u0005\u0004\u0003\u0002>F\u0005\b\u0005\u0002?@\u0007\t\u0002\u0002",
"@F\b\u0006\u0001\u0002AB\u0007\n\u0002\u0002BF\b\u0006\u0001\u0002C",
"D\u0007\u000b\u0002\u0002DF\b\u0006\u0001\u0002E9\u0003\u0002\u0002",
"\u0002E;\u0003\u0002\u0002\u0002E=\u0003\u0002\u0002\u0002E>\u0003\u0002",
"\u0002\u0002E?\u0003\u0002\u0002\u0002EA\u0003\u0002\u0002\u0002EC\u0003",
"\u0002\u0002\u0002F\u000b\u0003\u0002\u0002\u0002\b\u000e\u0016\u001d",
"07E"].join("");
"\u0002\u0007\u0002\u0004\u0006\b\n\u0002\u0002\u0002M\u0002\u000e\u0003",
"\u0002\u0002\u0002\u0004\u001d\u0003\u0002\u0002\u0002\u0006\u001f\u0003",
"\u0002\u0002\u0002\b7\u0003\u0002\u0002\u0002\nE\u0003\u0002\u0002\u0002",
"\f\u000f\u0005\u0004\u0003\u0002\r\u000f\u0005\b\u0005\u0002\u000e\f",
"\u0003\u0002\u0002\u0002\u000e\r\u0003\u0002\u0002\u0002\u000f\u0003",
"\u0003\u0002\u0002\u0002\u0010\u0011\u0007\u0003\u0002\u0002\u0011\u0016",
"\u0005\u0006\u0004\u0002\u0012\u0013\u0007\u0004\u0002\u0002\u0013\u0015",
"\u0005\u0006\u0004\u0002\u0014\u0012\u0003\u0002\u0002\u0002\u0015\u0018",
"\u0003\u0002\u0002\u0002\u0016\u0014\u0003\u0002\u0002\u0002\u0016\u0017",
"\u0003\u0002\u0002\u0002\u0017\u0019\u0003\u0002\u0002\u0002\u0018\u0016",
"\u0003\u0002\u0002\u0002\u0019\u001a\u0007\u0005\u0002\u0002\u001a\u001e",
"\u0003\u0002\u0002\u0002\u001b\u001c\u0007\u0003\u0002\u0002\u001c\u001e",
"\u0007\u0005\u0002\u0002\u001d\u0010\u0003\u0002\u0002\u0002\u001d\u001b",
"\u0003\u0002\u0002\u0002\u001e\u0005\u0003\u0002\u0002\u0002\u001f ",
"\u0007\f\u0002\u0002 !\b\u0004\u0001\u0002!\"\u0007\u0006\u0002\u0002",
"\"#\u0005\n\u0006\u0002#$\b\u0004\u0001\u0002$\u0007\u0003\u0002\u0002",
"\u0002%&\u0007\u0007\u0002\u0002&\'\b\u0005\u0001\u0002\'(\u0005\n\u0006",
"\u0002(0\b\u0005\u0001\u0002)*\u0007\u0004\u0002\u0002*+\b\u0005\u0001",
"\u0002+,\u0005\n\u0006\u0002,-\b\u0005\u0001\u0002-/\u0003\u0002\u0002",
"\u0002.)\u0003\u0002\u0002\u0002/2\u0003\u0002\u0002\u00020.\u0003\u0002",
"\u0002\u000201\u0003\u0002\u0002\u000213\u0003\u0002\u0002\u000220\u0003",
"\u0002\u0002\u000234\u0007\b\u0002\u000248\u0003\u0002\u0002\u00025",
"6\u0007\u0007\u0002\u000268\u0007\b\u0002\u00027%\u0003\u0002\u0002",
"\u000275\u0003\u0002\u0002\u00028\t\u0003\u0002\u0002\u00029:\u0007",
"\f\u0002\u0002:F\b\u0006\u0001\u0002;<\u0007\r\u0002\u0002<F\b\u0006",
"\u0001\u0002=F\u0005\u0004\u0003\u0002>F\u0005\b\u0005\u0002?@\u0007",
"\t\u0002\u0002@F\b\u0006\u0001\u0002AB\u0007\n\u0002\u0002BF\b\u0006",
"\u0001\u0002CD\u0007\u000b\u0002\u0002DF\b\u0006\u0001\u0002E9\u0003",
"\u0002\u0002\u0002E;\u0003\u0002\u0002\u0002E=\u0003\u0002\u0002\u0002",
"E>\u0003\u0002\u0002\u0002E?\u0003\u0002\u0002\u0002EA\u0003\u0002\u0002",
"\u0002EC\u0003\u0002\u0002\u0002F\u000b\u0003\u0002\u0002\u0002\b\u000e",
"\u0016\u001d07E"].join("");

@@ -82,0 +82,0 @@

@@ -60,22 +60,5 @@ let W = require("./walker");

// returns an array of nodes where their parents have no protos, skipping the definition name
function getElementPrefix(path) {
let R = []
path=path.slice(1) // remove definitions
let parent = model.definitions[path[0]]; // start with the definition
W.forEach(path, (index,item) => {
if(!parent) return;
if(index==="0") return; // ignore the definition
if(!Object.getPrototypeOf(parent))
R.push(item)
parent = parent[item]
})
return R;
}
function elementName(name, node, path) {
let nameParts = getElementPrefix(path);
node.name = {
id: name,
element:nameParts[-1]
id: name
};

@@ -200,2 +183,5 @@ U.setLocation(node.name, path.concat(name), U.WILO_FIRST)

}
if(getLastElement(PATH) === "mixin") {
augmentElement(key, node, PATH);
}
if(getLastElement(PATH) === "enum") {

@@ -212,9 +198,18 @@ augmentEnumItem(key, node, PATH);

}) // forEach
}, (path/*,obj*/) => { // check function
}, (path,obj) => { // check function
let le = U.getLastElement(path)
if(le[0]==="@")
return false; // do not walk annotations
if(U.isAugmented(obj)) {
return false;
}
return true;
}) // walkWithPath
W.walkWithPath(model, (isNode,PATH,NODE) => {
if(U.isAugmented(NODE)) {
U.unsetAugmented(NODE)
}
})
if(model.namespace) {

@@ -221,0 +216,0 @@ model.namespace = {

@@ -63,3 +63,3 @@ let W = require("./walker");

let X = node[name];
X.forEach( (Y, iY) => augmentExpressionItem(Y, path.concat(name,iY)))
X.forEach( (Y, iY) => augmentExpressionItem(Y, ""+iY, path.concat(name)))
}

@@ -245,3 +245,4 @@

function augmentExpressionItem(val, path) {
function augmentExpressionItem(val, name, Path) {
let path = Path.concat(name)
let location = U.newLocation(path)

@@ -277,2 +278,4 @@ if(val === null)

}
} else if(val.hasOwnProperty("SELECT")) {
return query(val,undefined,path)
}

@@ -285,3 +288,3 @@ throw Error("augmentExpressionItem failed: "+JSON.stringify(val)+path.join("/"))

if(X.val)
node[name] = augmentExpressionItem(X,path.concat(name,"val"))
node[name] = augmentExpressionItem(X,"val",path.concat(name))
else

@@ -294,5 +297,5 @@ modifyExpression(node,name,path);

if(!Array.isArray(X)) {
node[name] = augmentExpressionItem(X,path.concat(name))
node[name] = augmentExpressionItem(X,name,path)
} else {
node[name] = augmentExpression(X, path.concat(name));
node[name] = augmentExpression(X, name, path);
}

@@ -315,36 +318,101 @@ }

throw Error("Expression should be an array");
return X.map((Y,I) => augmentExpressionItem(Y, path.concat(""+I)))
return X.map((Y,I) => augmentExpressionItem(Y, I, path))
}
function query(node, name, path) {
let location = U.newLocation(path.concat(name), U.WILO_FULL)
let selectPath = path.concat([name,"SELECT"]);
function query(node, name, Path) {
let path=Path;
if(name!==undefined)
path=Path.concat(name);
let location = U.newLocation(path, U.WILO_FULL)
let Q;
if(name===undefined)
Q=node;
else
Q = node[name];
if(typeof Q !== "object")
throw Error("Expects object as query");
if(Q) {
let SET = Q.SET;
if(SET !== undefined) {
if(SET.args.length>1) {
SET.args.forEach( (X,I) => {
query(SET.args,""+I, path.concat(["SET","args"]))
})
let R = {
op: {val:"subquery", location},
location,
args:SET.args
};
delete SET.args;
node[name] = R;
U.setAugmented(R)
if(name!==undefined)
node[name] = R;
return R;
}
Q=SET.args[0];
path = path.concat(["SET","args","0"])
}
}
let selectPath = path.concat("SELECT");
let fromPath = selectPath.concat("from");
let columnsPath = selectPath.concat("columns");
let locationFrom = U.newLocation(fromPath, U.WILO_FULL)
function getQuerySource(q) {
function augmentQuerySourceFrom(q) {
let qo = q.SELECT;
if(!qo)
throw Error("Missing SELECT in query") //TODO move to validator
throw Error("Missing SELECT in query"+JSON.stringify(q)) //TODO move to validator
if(!qo.from)
throw Error("Missing FROM in SELECT") //TODO move to validator
if(qo.from.SELECT) {
query(qo,"from",path.concat("SELECT"));
return [qo.from]
}
if(qo.from.join) {
let args=qo.from.args.map(A => {
let path = A.ref.map(R => {
return {id:R,location}
})
return {path,name:{id:A.as},location}
})
let onPath = selectPath.concat(["from","on"]);
let on = augmentXPR(qo.from.on, onPath)
return [
{
op:{val:"join", location},
join:"leftOuter",
args,
on
}
]
}
if(!qo.from.ref)
throw Error("Missing reference in SELECT.from") //TODO move to validator
throw Error("Missing reference in SELECT.from: "+JSON.stringify(qo)) //TODO move to validator
if(qo.from.ref) {
let ref = qo.from.ref;
let refPath = fromPath.concat("ref")
return ref.map( (X,I) => {
return {
let path = ref.map( (X,I) => {
let R = {
id:X,
location:U.newLocation(refPath.concat(I), U.WILO_FULL)
}
if(X.id) R.id=X.id; //for filter select from E[a=2] -> ref:[{id:E,where...}]
if(X.where) {
R.where=augmentXPR(X.where,refPath.concat(I,"where"))
}
//TODO cardinality
return R;
})
return [ {
path,
location: locationFrom
}]
}
return undefined;
throw Error("Unknown query: "+JSON.stringify(q))
}
let newFrom = augmentQuerySourceFrom(Q);
let src = getQuerySource(node.query);
let all;
let columns = node.query.SELECT.columns;
let columns = Q.SELECT.columns;
let elements = Object.create(null);

@@ -370,3 +438,3 @@ if(columns!==undefined) {

elementName.$inferred="as";
elements[elementNameId] = {
let E = {
value: {path, location: elementNameLocation},

@@ -377,2 +445,3 @@ name: elementName,

};
elements[elementNameId] = E;
}

@@ -390,5 +459,4 @@ })

}
let from = node.query.SELECT.from;
let from = Q.SELECT.from;
let fromName;
let locationFrom = U.newLocation(fromPath, U.WILO_FULL)
if(from.as) {

@@ -401,5 +469,7 @@ fromName = {

let where = augmentXPR(node.query.SELECT.where, selectPath.concat("where"))
let where = augmentXPR(Q.SELECT.where, selectPath.concat("where"))
let orderBy = node.query.SELECT.orderBy;
let mixin = Q.SELECT.mixin;
let orderBy = Q.SELECT.orderBy;
if(orderBy !== undefined) {

@@ -445,3 +515,3 @@ let orderByPath = selectPath.concat("orderBy");

let limitOffset = node.query.SELECT.limit;
let limitOffset = Q.SELECT.limit;
let limit,offset;

@@ -456,3 +526,3 @@ if(limitOffset !== undefined) {

let excluding = node.query.SELECT.excluding
let excluding = Q.SELECT.excluding
if(excluding != undefined) {

@@ -469,3 +539,2 @@ let r = Object.create(null);

}
let R = {

@@ -475,7 +544,10 @@ op: {val:"query", location},

elements,
from:[ {
path:src,
location: locationFrom
}]
from:newFrom
};
if(Q.as) { // query alias
let location = U.newLocation(path.concat("as"), U.WILO_FULL)
R.name = {id:node[name].as, location}
}
if(columns === undefined)
R.all = { val: 'implicit', location }
if (fromName)

@@ -488,2 +560,6 @@ R.from[0].name = fromName;

R.where=where;
if(mixin) {
R.mixin=mixin;
Object.setPrototypeOf(mixin,null)
}
if(orderBy)

@@ -497,3 +573,6 @@ R.orderBy=orderBy;

R.exclude = excluding;
node[name] = R;
U.setAugmented(R)
if(name!==undefined)
node[name] = R;
return R;
}

@@ -500,0 +579,0 @@

@@ -59,2 +59,7 @@ function newUtils(model) {

unsetAugmented: function (node) {
if(!(delete node.augmented))
throw Error("unsetAugmented failed");
},
setAugmented: function (node) {

@@ -69,2 +74,3 @@ // hidden property "augmented" which prevents recursive augmentation

writable: false,
configurable: true,
value: true

@@ -71,0 +77,0 @@ });

@@ -302,7 +302,9 @@ // Transform augmented CSN into compact "official" CSN

let model = compact( ...args );
let result = Object.create(null);
let definitions = model.definitions;
for (let k of Object.keys( definitions ).sort())
result[k] = normalizeNode( definitions[k] );
model.definitions = result;
if (model.definitions) {
let result = Object.create(null);
let definitions = model.definitions;
for (let k of Object.keys( definitions ).sort())
result[k] = normalizeNode( definitions[k] );
model.definitions = result;
}
return model;

@@ -309,0 +311,0 @@ }

@@ -1041,2 +1041,5 @@ //Notes:

},
"origin": {
"type": "string"
},
"kind": {

@@ -1043,0 +1046,0 @@ "const": "enum",

@@ -117,3 +117,3 @@ // Transform augmented CSN into compact "official" CSN

function compactModel( model, options = model.options || {} ) {
csn_gensrc = options.disablePropagate;
csn_gensrc = options.toCsn && options.toCsn.gensrc;
//strict = options.testMode;

@@ -455,3 +455,3 @@ let csn = {};

!args[0].all === !node.all && args[0].args)
args = [ ...args[0], ...args.slice(1) ]
args = [ ...args[0].args, ...args.slice(1) ]
if (node.op.val === 'unionAll') // TODO grammar: set DISTINCT - quantifier: 'all'|'distinct'

@@ -503,3 +503,3 @@ csn.all = true;

while (node.join === 'cross' && args[0] && args[0].join === node.join && args[0].args)
args = [ ...args[0], ...args.slice(1) ]
args = [ ...args[0].args, ...args.slice(1) ]
let join = { join: joinTrans[node.join] || node.join, args: node.args.map( from ) };

@@ -506,0 +506,0 @@ set( 'on', join, node );

@@ -9,3 +9,3 @@ // Wrapper around generated ANTLR parser

var { CompileMessage } = require('../base/messages');
var { getMessageFunction, CompileMessage } = require('../base/messages');
var errorStrategy = require('./errorStrategy');

@@ -20,17 +20,8 @@

super(...args);
this.messages = [];
}
// Push message `msg` with location `loc` to array of errors:
message( msg, loc, severity ) {
this.messages.push( new CompileMessage( loc, msg, severity ) );
}
// method which is called by generated parser:
// method which is called by generated parser with --trace-parser[-amg]:
syntaxError( recognizer, offendingSymbol, line, column, msg, e ) {
var loc = recognizer.tokenLocation( offendingSymbol );
//console.log(e); throw new CompileMessage( loc, msg );
var err = new CompileMessage( loc, msg[0].toUpperCase() + msg.slice(1) );
if (e && e.expectedTokens)
err.expectedTokens = e.expectedTokens;
this.messages.push( err );
if (!(e instanceof CompileMessage)) // not already reported
recognizer.message( null, offendingSymbol, msg );
}

@@ -103,5 +94,7 @@ }

parser.filename = filename;
parser.options = options;
parser.$message = getMessageFunction( parser ); // sets parser.messages
initTokenRewrite( parser, tokenStream );
parser.options = options;
parser.filename = filename;
// comment the following 2 lines if you want to output the parser errors directly:

@@ -111,5 +104,2 @@ parser.messageErrorListener = errorListener;

parser.match = errorStrategy.match;
// parser.consume = errorStrategy.consume;
// parser.exitRule = errorStrategy.exitRule;
// parser.epsilon = errorStrategy.epsilon; // for empty alts
parser._interp.predictionMode = antlr4.atn.PredictionMode.SLL;

@@ -134,4 +124,5 @@ // parser._interp.predictionMode = antlr4.atn.PredictionMode.LL_EXACT_AMBIG_DETECTION;

parser.removeErrorListeners();
parser.addErrorListener( errorListener );
parser.avoidErrorListeners = true;
}
parser.addErrorListener( errorListener );

@@ -148,3 +139,3 @@ if (options.parseListener) {

ast.messages = errorListener.messages;
ast.messages = parser.messages;
if (options.attachTokens === true || options.attachTokens === filename)

@@ -151,0 +142,0 @@ ast.tokenStream = tokenStream;

@@ -39,26 +39,5 @@ // Error strategy with special handling for (non-reserved) keywords

// Remember context for potential error message
function epsilon() {
let lt1 = this._input.LT(1);
if (this.state >= 0 && lt1 !== this.$exitToken) {
this.$exitToken = lt1;
this.$exitState = this.state;
this.$exitCtx = this._ctx;
}
}
var SEMI = null;
var RBRACE = null;
function exitRule() {
epsilon.call( this );
antlr4.Parser.prototype.exitRule.call( this );
}
function consume() {
// Unfortunately, ANTLR does not generate "this.state = <endState>" after
// this.consume / this.match if nothing can follow the token. But exitRule
// needs to know whether the token has been consumed or not.
let t = antlr4.Parser.prototype.consume.call( this );
this.state = -1; // illegal state stands for <endState>
return t;
}
// Match current token against token type `ttype` and consume it if successful.

@@ -101,2 +80,3 @@ // Also allow to match keywords as identifiers. This function should be set as

reportMissingToken,
reportIgnoredWith,
// getErrorRecoverySet,

@@ -107,2 +87,3 @@ consumeUntil,

getExpectedTokensForMessage,
getTokenDisplay,
constructor: KeywordErrorStrategy

@@ -112,3 +93,3 @@ });

// Attemp to recover from problems in subrules, except if rule has defined a
// local variable `LeaveLoop` with truthy value
// local variable `_sync` with value 'nop'
function sync( recognizer ) {

@@ -126,11 +107,16 @@ // If already recovering, don't try to sync

var nextTokens = recognizer.atn.nextTokens(s);
// console.log('SYNC:', recognizer._ctx._sync, s.stateType, token.text, intervalSetToArray( recognizer, nextTokens ))
// console.log(antlr4.Token.EPSILON, nextTokens.contains(antlr4.Token.EPSILON),nextTokens)
if (nextTokens.contains(token.type)) { // we are sure the token matches
recognizer.$nextTokensToken = null;
recognizer.$nextTokensState = ATNState.INVALID_STATE_NUMBER;
recognizer.$nextTokensContext = null;
// console.log('REMOVE:',token.type,recognizer.state)
if (token.text === '}' && recognizer.$nextTokensToken !== token &&
nextTokens.contains(SEMI)) {
// if the '}' could be matched alternative to ';', we had an opt ';' (rule requiredSemi)
recognizer.$nextTokensToken = token;
recognizer.$nextTokensState = recognizer.state;
recognizer.$nextTokensContext = recognizer._ctx;
}
return;
}
// TODO: expected token is identifier, current is KEYWORD
if (nextTokens.contains(antlr4.Token.EPSILON)) {

@@ -146,5 +132,4 @@ if (recognizer.$nextTokensToken !== token) {

if (recognizer._ctx.LeaveLoop)
if (recognizer._ctx._sync === 'nop')
return;
switch (s.stateType) {

@@ -156,5 +141,12 @@ case ATNState.BLOCK_START:

// report error and recover if possible
if( this.singleTokenDeletion(recognizer) !== null) {
if( token.text !== '}' && // do not just delete a '}'
this.singleTokenDeletion(recognizer) !== null) { // also calls reportUnwantedToken
return;
} else {
}
else if (recognizer._ctx._sync === 'recover') {
this.reportInputMismatch( recognizer, new InputMismatchException(recognizer) );
this.consumeUntil( recognizer, nextTokens );
return;
}
else {
throw new InputMismatchException(recognizer);

@@ -174,2 +166,5 @@ }

}
// singleTokenInsertion called by recoverInline (called by match / in else)
// Report `NoViableAltException e` signalled by parser `recognizer`

@@ -195,8 +190,16 @@ function reportNoViableAlternative( recognizer, e ) {

this.getExpectedTokensForMessage( recognizer, e.offendingToken, deadEnds );
var msg = "Mismatched input " + this.getTokenErrorDisplay(e.offendingToken);
if (expecting) {
msg += " expecting " + expecting.toString(recognizer.literalNames, recognizer.symbolicNames);
e.expectedTokens = intervalSetToArray( recognizer, expecting );
let offending = this.getTokenDisplay( e.offendingToken, recognizer );
let err;
if (expecting && expecting.length) {
err = recognizer.message( 'syntax-mismatched-token', e.offendingToken,
{ offending, expecting: expecting.join(', ') },
'Error', 'Mismatched $(OFFENDING), expecting $(EXPECTING)' );
err.expectedTokens = expecting;
}
recognizer.notifyErrorListeners(msg, e.offendingToken, e);
else { // should not really happen anymore... -> no messageId !
err = recognizer.message( null, e.offendingToken, { offending },
'Error', 'Mismatched $(OFFENDING)' );
}
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
recognizer.notifyErrorListeners( err.message, e.offendingToken, err );
}

@@ -206,14 +209,15 @@

function reportUnwantedToken( recognizer ) {
if (this.inErrorRecoveryMode(recognizer)) {
if (this.inErrorRecoveryMode(recognizer))
return;
}
this.beginErrorCondition(recognizer);
var t = recognizer.getCurrentToken();
var tokenName = this.getTokenErrorDisplay(t);
var expecting = this.getExpectedTokensForMessage( recognizer, t );
var e = new antlr4.error.InputMismatchException( recognizer );
e.expectedTokens = intervalSetToArray( recognizer, expecting );
var msg = "Extraneous input " + tokenName + " expecting " +
expecting.toString(recognizer.literalNames, recognizer.symbolicNames);
recognizer.notifyErrorListeners(msg, t, e);
var token = recognizer.getCurrentToken();
var expecting = this.getExpectedTokensForMessage( recognizer, token );
var offending = this.getTokenDisplay( token, recognizer );
let err = recognizer.message( 'syntax-extraneous-token', token,
{ offending, expecting: expecting.join(', ') },
'Error', 'Extraneous $(OFFENDING), expecting $(EXPECTING)' );
err.expectedTokens = expecting;
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
recognizer.notifyErrorListeners( err.message, token, err );
}

@@ -223,16 +227,27 @@

function reportMissingToken( recognizer ) {
if ( this.inErrorRecoveryMode(recognizer)) {
if ( this.inErrorRecoveryMode(recognizer))
return;
}
this.beginErrorCondition(recognizer);
var t = recognizer.getCurrentToken();
var expecting = this.getExpectedTokensForMessage( recognizer, t );
var e = new antlr4.error.InputMismatchException( recognizer );
e.expectedTokens = intervalSetToArray( recognizer, expecting );
var msg = "Missing " + expecting.toString(recognizer.literalNames, recognizer.symbolicNames) +
" at " + this.getTokenErrorDisplay(t);
recognizer.notifyErrorListeners(msg, t, e);
var token = recognizer.getCurrentToken();
var expecting = this.getExpectedTokensForMessage( recognizer, token );
var offending = this.getTokenDisplay( token, recognizer );
// TODO: if non-reserved keyword will not been parsed as keyword, use Identifier for offending
let err = recognizer.message( 'syntax-missing-token', token,
{ offending, expecting: expecting.join(', ') },
'Error', 'Missing $(EXPECTING) before $(OFFENDING)' );
err.expectedTokens = expecting;
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
recognizer.notifyErrorListeners( err.message, token, err );
}
var SEMI = null;
function reportIgnoredWith( recognizer, t ) {
let next = recognizer._interp.atn.states[ recognizer.state ].transitions[0].target;
recognizer.state = next.stateNumber; // previous match() does not set the state
let expecting = this.getExpectedTokensForMessage( recognizer, t );
let m = recognizer.message( 'syntax-ignored-with', t,
{ offending: "';'", expecting: expecting.join(', ') },
'Warning', `Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous WITH` );
m.expectedTokens = expecting;
}

@@ -242,9 +257,28 @@ function consumeUntil( recognizer, set ) {

SEMI = recognizer.literalNames.indexOf( "';'" );
if (SEMI < 1 || set.contains(SEMI)) {
if (RBRACE == null)
RBRACE = recognizer.literalNames.indexOf( "'}'" );
// let s=this.getTokenDisplay( recognizer.getCurrentToken(), recognizer );
if (SEMI < 1 || RBRACE < 1) {
super1.consumeUntil.call( this, recognizer, set );
}
else if (set.contains(SEMI)) { // do not check for RBRACE here!
super1.consumeUntil.call( this, recognizer, set );
// console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(), recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));
}
else {
set.addOne( SEMI );
super1.consumeUntil.call( this, recognizer, set );
// if (recognizer.getTokenStream().LA(1) === SEMI) console.log( 'CONSUME: Semi' )
// DO NOT modify input param `set`, as the set might be cached in the ATN
let stop = new IntervalSet.IntervalSet();
stop.addSet( set );
stop.removeOne( recognizer.constructor.Identifier );
stop.addOne( SEMI );
// I am not that sure whether to add RBRACE...
stop.addOne( RBRACE );
super1.consumeUntil.call( this, recognizer, stop );
if (recognizer.getTokenStream().LA(1) === SEMI ||
recognizer.getTokenStream().LA(1) === RBRACE && !set.contains(RBRACE)) {
recognizer.consume();
this.reportMatch(recognizer); // we know current token is correct
}
// if matched '}', also try to match next ';' (also matches double ';')
if (recognizer.getTokenStream().LA(1) === SEMI) {

@@ -254,2 +288,4 @@ recognizer.consume();

}
// console.log('CONSUMED:',s,this.getTokenDisplay( recognizer.getCurrentToken(), recognizer ),recognizer.getCurrentToken().line);
// throw new Error('Sync')
}

@@ -263,2 +299,4 @@ }

// We now also allow keywords if the Identifier is expected.
// Called by match() and in generated parser in "else part" before consume()
// for ( TOKEN1 | TOKEN2 )
function recoverInline( recognizer ) {

@@ -303,6 +341,46 @@ var identType = recognizer.constructor.Identifier;

}
if (recognizer.$nextTokensToken === recognizer.$removeSemiFor)
names = names.filter( n => n !== "';'" && n !== "'}'" );
else if (names.includes("';'"))
names = names.filter( n => n !== "'}'" );
names.sort( (a, b) => tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1 );
return names;
}
const token1sort = {
// 0: Identifier, Number, ...
// 1: separators:
',': 1, '.': 1, ':': 1, ';': 1,
// 2: parentheses:
'(': 2, ')': 2, '[': 2, ']':2, '{': 2, '}': 2,
// 3: special:
'!': 3, '#': 3, '$': 3, '?': 3, '@': 3,
// 4: operators:
'*': 4, '+': 4, '-': 4, '/': 4, '<': 4, '=': 4, '>': 4, '|': 4,
// 8: KEYWORD
// 9: <EOF>
}
function tokenPrecedence( name ) {
if (name.length < 2 || name === '<EOF>')
return '9' + name;
let prec = token1sort[ name.charAt(1) ];
if (prec)
return '' + prec + name;
else
return (name.charAt(1) < 'a' ? '8' : '0') + name;
}
function getTokenDisplay( token, recognizer ) {
if (!token)
return '<EOF>';
let t = token.type;
if (t === antlr4.Token.EOF || t === antlr4.Token.EPSILON )
return '<EOF>';
else if (token.text === '.') // also for DOTbeforeBRACE
return "'.'";
else
return recognizer.literalNames[t] || recognizer.symbolicNames[t];
}
// Return an IntervalSet of token types which the parser had expected. Do not

@@ -319,3 +397,3 @@ // include non-reserved keywords if not mentioned explicitly (i.e. other than

if (recognizer.state < 0)
return null;
return [];
if (recognizer.state >= atn.states.length)

@@ -328,3 +406,3 @@ throw( 'Invalid state number ' + recognizer.state + ' for ' +

if (!identType || !beforeUnreserved || beforeUnreserved + 2 > identType)
return super1.getExpectedTokens.call( this, recognizer );
return intervalSetToArray( recognizer, super1.getExpectedTokens.call( this, recognizer ) );

@@ -358,3 +436,3 @@ var ll1 = new antlr4_LL1Analyzer(atn);

// console.log(state, recognizer.$nextTokensState, expected.toString(recognizer.literalNames, recognizer.symbolicNames));
return expected;
return intervalSetToArray( recognizer, expected );

@@ -387,10 +465,5 @@ // Add an interval `v` to the IntervalSet `this`. If `v` contains the token

// probably overwrite getTokenErrorDisplay() - use token text
module.exports = {
epsilon,
exitRule,
consume,
match,
KeywordErrorStrategy
};

@@ -8,2 +8,3 @@ // Generic ANTLR parser class with AST-building functions

var antlr4 = require('antlr4');
var ATNState = require('antlr4/atn/ATNState').ATNState;
var { addToDictWithIndexNo } = require('../base/dictionaries');

@@ -47,4 +48,6 @@

setOnce,
notYet,
hanaFlavorOnly,
noAssignmentInSameLine,
noSemicolonHere,
isStraightBefore,
constructor: GenericAntlrParser // keep this last

@@ -89,33 +92,68 @@ });

// Push message `msg` with location `loc` to array of errors:
function message( msg, loc, severity ) {
if (!this.options.parseOnly) // TODO: remove this test
this.messageErrorListener.message( msg, loc, severity );
function message( id, loc, ...args ) {
return this.$message( id, // function $message is set in antlrParser.js
(loc instanceof antlr4.CommonToken) ? this.tokenLocation(loc) : loc,
...args );
}
// Push a message to the array of errors complaining that language construct
// 'feature' is not yet supported (using the location `loc`, which can also be a token for its locatio'), unless
// option 'parseOnly' or any of the options from 'optionsArray' are set.
function notYet( feature, loc, optionsArray=[] ) {
if (this.options.parseOnly) {
// Generally ignore if only parsing
// Use the following functions for language constructs which we (currently)
// just being able to parse, in able to run tests from HANA CDS. As soon as we
// create ASTs for the language construct and put it into a CSN, a
// corresponding check should actually be inside the compiler, because the same
// language construct can come from a CSN as source.
// TODO: this is not completely done this way
function hanaFlavorOnly( text, ...tokens ) {
if (!text || this.options.hanaFlavor)
return;
if (typeof text !== 'string') {
tokens = [ text, ...tokens ];
text = tokens.map( t => t.text.toUpperCase() ).join(' ') + ' is not supported';
}
for (let option of optionsArray) {
// Grammar says to ignore for this option
if (this.options[option]) {
return;
}
}
this.messageErrorListener.message( `${feature} not supported yet`,
(loc instanceof antlr4.CommonToken) ? this.tokenLocation(loc) : loc );
this.message( null, this.tokenLocation( tokens[0], tokens[ tokens.length-1 ] ), text );
}
function noSemicolonHere() {
let handler = this._errHandler;
var t = this.getCurrentToken();
if (t.text === ';')
this.messageErrorListener.message( `Unexpected ';' - previous keyword 'with' is ignored`,
this.tokenLocation(t), 'Warning' );
// TODO remove ';' from set of expected tokens
this.$removeSemiFor = t;
this.$nextTokensToken = t;
this.$nextTokensContext = null; // match() of WITH does not reset
this.$nextTokensState = ATNState.INVALID_STATE_NUMBER;
if (t.text === ';' && handler && handler.reportIgnoredWith ) {
handler.reportIgnoredWith( this, t );
}
}
// // Special function for rule `requiredSemi` before return $ctx
// function braceForSemi() {
// if (RBRACE == null)
// RBRACE = this.literalNames.indexOf( "'}'" );
// console.log(RBRACE)
// // we are called before match('}') and this.state = ...
// let atn = this._interp.atn;
// console.log( atn.nextTokens( atn.states[ this.state ], this._ctx ) )
// let next = atn.states[ this.state ].transitions[0].target;
// // if a '}' is not possible in the grammar after the fake-'}', throw error
// if (!atn.nextTokens( next, this._ctx ).contains(RBRACE))
// console.log( atn.nextTokens( next, this._ctx ) )
// // throw new antlr4.error.InputMismatchException(this);
// }
function noAssignmentInSameLine() {
var t = this.getCurrentToken();
if (t.text === '@' && t.line <= this._input.LT(-1).line)
this.message( 'syntax-anno-same-line', t, {},
'Warning', `Annotation assignment belongs to next statement` );
}
// Use after matching ',' to allow ',' in front of the closing paren. Be sure
// that you know what to do if successful - break/return/... = check the
// generated grammar; inside loops, you can use `break`. This function is
// still the preferred way to express an optional ',' at the end, because it
// does not influence the error reporting. It might also allow to match
// reserved keywords, because there is no ANTLR generated decision in front of it.
function isStraightBefore( closing ) {
return this.getCurrentToken().text === closing;
}
// Attach location matched by current rule to node `art`. If a location is

@@ -170,2 +208,4 @@ // already provided, only set the end location. Use this function only

function combinedLocation( start, end ) {
if (!start || !start.location)
start = { location: this.startLocation() };
return {

@@ -187,4 +227,4 @@ filename: start.location.filename,

if (!id) {
this.message( "Quoted identifier must contain at least one character",
this.tokenLocation( token ) );
this.message( 'syntax-empty-ident', token, {},
'Error', 'Quoted identifier must contain at least one character' );
}

@@ -223,4 +263,6 @@ return { id, quoted: true, location: this.tokenLocation( token ) };

if (p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val))
this.message( p.test_msg, location );
// TODO: make tests available for CSN parser
if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
!this.options.parseOnly)
this.message( null, location, p.test_msg ); // TODO: message id

@@ -231,7 +273,7 @@ if (p.unexpected_char)

if (~idx) {
this.message( p.unexpected_msg, {
this.message( null, { // TODO: message id
filename: location.filename,
start: atChar( idx ),
end: atChar( idx + (val[idx] == '\'' ? 2 : 1) )
} );
}, p.unexpected_msg );
}

@@ -292,3 +334,3 @@ }

}
else if (kind) {
else if (kind || this.options.parseOnly) {
addToDictWithIndexNo( parent, env, art.name.id, art );

@@ -300,7 +342,10 @@ }

if (kind === 0)
this.message( `Duplicate value for view parameter "${name}"`, loc );
this.message( 'duplicate-argument', loc, { name },
'Error', 'Duplicate value for parameter $(NAME)' );
else if (kind === '')
this.message( `Duplicate EXCLUDING for source element "${name}"`, loc );
this.message( 'duplicate-excluding', loc, { name },
'Error', 'Duplicate EXCLUDING for source element $(NAME)' );
else
this.message( `Duplicate assignment for structure property "${name}"`, loc );
this.message( 'duplicate-prop', loc, { name },
'Error', 'Duplicate value for structure property $(NAME)' );
} );

@@ -350,3 +395,3 @@ }

if (val != null &&
(typeof val !== "object" ||
(typeof val !== 'object' ||
(val instanceof Array ? val.length : Object.getOwnPropertyNames(val).length) ) ) {

@@ -374,3 +419,4 @@ target[key] = val;

if (prev) {
this.message( `Option ${prev.option} has already been specified`, loc );
this.message( 'syntax-repeated-option', loc, { option: prev.option },
'Error', 'Option $(OPTION) has already been specified' );
}

@@ -377,0 +423,0 @@ if (typeof value === 'boolean') {

@@ -58,3 +58,3 @@ // Main entry point for the Research Vanilla CDS Compiler

var { postProcessForBackwardCompatibility } = require('./transform/forOdata');
var { getDefaultTntFlavorOptions } = require('./transform/tntSpecific');
var { getDefaultTntFlavorOptions, propagateIncludesForTnt } = require('./transform/tntSpecific');
var csn2edm = require('./edm/csn2edm');

@@ -85,4 +85,5 @@ const emdx2csn = require('./edm/annotations/edmx2csnNew'); // translate edmx annotations into csn

model.$frontend = 'json';
if(model.version && model.version.csn === "0.1.99")
options.newCsn=true; // force new version TODO optimize?
// TODO: I (CW) do not think that the following is a good idea...
if (model.version && model.version.csn === "0.1.99")
options.newCsn = true; // force new version TODO optimize?
return model;

@@ -481,7 +482,3 @@ } else if (options.fallbackParser || ['.cds', '.hdbcds', '.hdbdd'].includes(ext))

if (options.newCsn)
model = propagator.propagate( model );
else if (!options.disablePropagate)
// TODO use new-style propagate also for TnT once actions/functions are propagated correctly
model = ((options.oldPropagate || options.tntFlavor) ? propagator.propagateAssignments : propagator.propagate)( model );
model = propagator.propagate( model );
if (!options.modelExtender || '$draft.cds' in sources)

@@ -582,2 +579,6 @@ return model;

if (!options.tntFlavor.skipPropagatingIncludes) {
propagateIncludesForTnt(result.csn);
}
// FIXME: For backward compatibility, replace the 'annotations.xml' in all services with the V4 version

@@ -584,0 +585,0 @@ // (unfortunately we used to deliver this really as a V4 version, which was probably unnecessary ...)

@@ -781,3 +781,5 @@

// date'2017-11-02'
return result + v.literal + "'" + v.val + "'";
// date('2017-11-02') if sqlite
return result + v.literal + `${options.toSql.dialect === 'sqlite' ?
"('" : "'"}` + v.val + `${options.toSql.dialect === 'sqlite' ? "')" : "'"}`;
} else if (v.literal == 'struct') {

@@ -784,0 +786,0 @@ // { foo: 1 }

@@ -134,3 +134,7 @@ "use strict";

// Perform implicit redirection of non-exposed association targets
addImplicitRedirections(model);
// Don't do it when producing SQL for sqlite, as there are no assocs to redirect any more
// (FIXME : ... and implicit redirection fails when translation assoc-to-join has already run)
if (!(options.toSql && options.toSql.dialect === 'sqlite')) {
addImplicitRedirections(model);
}

@@ -137,0 +141,0 @@ // Process all artifacts (pass 1)

@@ -108,3 +108,4 @@ 'use strict';

// If the managed association is NOT NULL, we give it a target min cardinality of 1
// if it didn't already have an explicitly specified min cardinality
// if it didn't already have an explicitly specified min cardinality.
// (No need to check again for min <= max cardinality, because max has already been checked to be > 0)
if (member.notNull) {

@@ -123,9 +124,2 @@ if (!member.cardinality) {

// min <= max cardinality
if(member.cardinality && member.cardinality.targetMin && member.cardinality.targetMax) {
if(member.cardinality.targetMin.val > member.cardinality.targetMax.val) {
signal(error`Element "${artifact.name.absolute}.${member.name.id}" target minimum cardinality must not be greater than target maximum cardinality`, member.cardinality.location);
}
}
// Entities only: Flatten structs used in paths

@@ -279,2 +273,6 @@ if (artifact.kind == 'entity') {

if (['element', 'key', 'param'].includes(member.kind)) {
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
if (member._flatElementNameWithDots) {
memberName = member._flatElementNameWithDots;
}
addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.toOdata.names), member);

@@ -450,2 +448,7 @@ }

}
// Make all non-key fields nullable
if (elem.notNull && !elem.key) {
elem.notNull.val = false;
}
}

@@ -452,0 +455,0 @@

@@ -19,5 +19,2 @@ 'use strict';

skipServiceIncludes: false, // if true: Do not include contexts into services via "@extends" annotation
skipPropagatingFromProjectionSrc: false, // if true: Do not propagate properties from source to projection
skipPropagatingFromInclude: false, // if true: Do not propagate properties from (first) included artifact
skipPropagatingActions: false, // if true: Do not propagate bound actions (and functions)
skipPropagatingIncludes: false, // if true: Do not propagate the list of included artifacts

@@ -192,5 +189,38 @@ skipNotPropagatingIndexableAnno: false, // if true: Do not make an exception for the propagation of the '@Indexable' annotation

// Within compact CSN 'csn', propagate the 'include' property along included entities and query sources,
// as the old implementation of propagation in the compiler did (ending up with the value of the
// 'includes' property from the lowest artifact in the chain that has one being copied to all above,
// which doesn't actually make much sense - but that's what TNT apparently got used to).
// Modify the model in place.
function propagateIncludesForTnt(csn) {
for (let name in csn.definitions) {
let artifact = csn.definitions[name];
propagateIncludesFrom(artifact);
}
function propagateIncludesFrom(artifact) {
if (artifact.includes) {
// Perform propagation for all of our own includes (if any)
for (let includeName of artifact.includes) {
propagateIncludesFrom(csn.definitions[includeName]);
}
// Take over the first include's includes (if any)
if (csn.definitions[artifact.includes[0]].includes) {
artifact.includes = csn.definitions[artifact.includes[0]].includes;
}
} else if (artifact.source) {
// Perform propagation for our projection source (if any)
propagateIncludesFrom(csn.definitions[artifact.source]);
// Take over our projectiuon source's includes (if any)
if (csn.definitions[artifact.source].includes) {
artifact.includes = csn.definitions[artifact.source].includes;
}
}
}
}
module.exports = {
getDefaultTntFlavorOptions,
transformTntExtensions,
propagateIncludesForTnt,
}

@@ -188,4 +188,14 @@ 'use strict';

let valueForeignKeyElementName = assoc.value.element.replace(/\./g, pathDelimiter) + fkSeparator + foreignKey.name.id;
// For the foreign key element, take the same path as for the assoc, just without the last step
let valueForeignKeyElementPath = [];
if (assoc.value.path) {
valueForeignKeyElementPath = cloneWithTransformations(assoc.value.path, {}).slice(0, -1);
// Take over _artifact links from original path, because flattenStructStepsInPath requires them
for (let i = 0; i < valueForeignKeyElementPath.length; i++) {
valueForeignKeyElementPath[i]._artifact = assoc.value.path[i]._artifact;
}
}
valueForeignKeyElementPath.push({ id: valueForeignKeyElementName });
foreignKeyElement.value = {
path: [{ id: valueForeignKeyElementName }],
path: valueForeignKeyElementPath,
absolute: assoc.value.absolute,

@@ -283,2 +293,4 @@ element: valueForeignKeyElementName,

}
// Preserve the generated element name as it would have been with 'hdbcds' names
setProp(flatElem, '_flatElementNameWithDots', elem.name.id + '.' + grandChildName);
result[flatElemName] = flatElem;

@@ -316,2 +328,4 @@ }

}
// Preserve the generated element name as it would have been with 'hdbcds' names
setProp(flatElem, '_flatElementNameWithDots', elem.name.id + '.' + childName);
result[flatElemName] = flatElem;

@@ -318,0 +332,0 @@ }

@@ -18,5 +18,2 @@ 'use strict'

if(!options.betaMode)
signal(error`Conversion of associations into JOINs is not supported yet`);
// Note: This is called from the 'forHana' transformations, so it is controlled by its options)

@@ -31,3 +28,3 @@ const pathDelimiter = (options.forHana && options.forHana.names == 'hdbcds') ? '.' : '_';

forEachDefinition(model, prepareAssociations);
forEachDefinition(model, transformView);
forEachDefinition(model, transformQueries);

@@ -38,3 +35,2 @@ // Throw up if we have errors

}
return model;

@@ -47,3 +43,3 @@

{
/* create the prefix string up to the main artifact which is
/* Create the prefix string up to the main artifact which is
prepended to all source side paths of the resulting ON condition

@@ -54,4 +50,6 @@ (cut off name.id from name.element)

// create path prefix tree for Foreign Keys, required to substitute aliases in ON cond calculation
// also very useful to detect fk overlaps
/*
Create path prefix tree for Foreign Keys, required to substitute
aliases in ON cond calculation, also very useful to detect fk overlaps.
*/
if(type.foreignKeys && !type.fkPathPrefixTree)

@@ -86,20 +84,16 @@ {

function transformView(art)
function transformQueries(art)
{
if(modelUtils.isView(art))
transformQueries(art.queries);
}
let queries = art.queries;
function transformQueries(queries)
{
if(queries.length>0)
if(queries && queries.length > 0)
{
/*
Setup QATs and leaf QAs (mixins, query, subqueries in from clause)
1a) First, mark all mixin assoc definitions with a pseudo QA that points to the assoc target.
1b) Next, for all paths in a query do flytrap checks and create the path prefix trees aka QATs.
Paths that start with a mixin assoc are Qat'ed into the mixin definition.
If a mixin assoc is published, its leaf Qat receives the pseudo QA(view) from the rootQat,
which is the mixin definition itself. See 1a)
1c) Finally create QAs for from clause subqueries, as they are not yet swept by the path walk
Setup QATs and leaf QAs (mixins, query, subqueries in from clause)
1a) Mark all mixin assoc definitions with a pseudo QA that points to the assoc target.
1b) For all paths in a query do flytrap checks and create the path prefix trees aka QATs.
Paths that start with a mixin assoc are Qat'ed into the mixin definition.
If a mixin assoc is published, its leaf Qat receives the pseudo QA(view) from the rootQat,
which is the mixin definition itself. See 1a)
1c) Create QAs for FROM clause subqueries, as they are not yet swept by the path walk
*/

@@ -115,3 +109,3 @@ let env = {

// 2) walk over each from table path, transform it into a join tree
// 2) Walk over each from table path, transform it into a join tree
env.walkover = { from:true, onCondFrom:false, select: false, filter: false };

@@ -121,11 +115,13 @@ env.callback = [ createInnerJoins ];

// 3) transform toplevel from block into cross join
// 3) Transform toplevel FROM block into cross join
queries.map(q => createCrossJoins(q));
// 4) transform all remaining join relevant paths into left outer joins and connect with
// FROM block join tree. Instead of walking paths it is sufficient to process the $qat of each tableAlias
// 4) Transform all remaining join relevant paths into left outer joins and connect with
// FROM block join tree. Instead of walking paths it is sufficient to process the $qat
// of each $tableAlias.
queries.map(q => createLeftOuterJoins(q, env));
// 5) rewrite original/native ON cond paths (same rewrite as with assoc ON cond path but with different table alias)
// 6) prepend table alias to all remaining paths
// 5) Rewrite ON condition paths that are part of the original FROM block
// (same rewrite as (injected) assoc ON cond paths but with different table alias).
// 6) Prepend table alias to all remaining paths
env.walkover = { from:false, onCondFrom:true, select: true, filter: false };

@@ -135,3 +131,3 @@ env.callback = [ rewriteGenericPaths ];

// 7) attach firstFilterConds to Where Condition.
// 7) Attach firstFilterConds to Where Condition.
queries.map(q => attachFirstFilterConditions(q));

@@ -145,5 +141,4 @@

{
// if the toplevel FROM block is a comma separated list and has more then one entry,
// If the toplevel FROM block is a comma separated list and has more then one entry,
// cross join list items and replace from array
if(query.op.val === 'query')

@@ -154,3 +149,3 @@ {

// recurse into sub queries
// Recurse into sub queries
query.queries.map(q => createCrossJoins(q));

@@ -160,3 +155,3 @@ }

// transform each from table path into a join tree and attach the tree to the path object
// Transform each FROM table path into a join tree and attach the tree to the path object
function createInnerJoins(fromPathNode, env)

@@ -170,3 +165,3 @@ {

// translate all join relevant query paths into left outer join tree and attach it to the lead query
// Translate all other join relevant query paths into left outer join tree and attach it to the lead query
function createLeftOuterJoins(query, env)

@@ -188,3 +183,3 @@ {

// recurse into sub queries
// Recurse into sub queries
query.queries.map(q => createLeftOuterJoins(q, env));

@@ -195,16 +190,15 @@ }

/*
Each leaf node of a table path must end in either a direct or target artifact. During mergePathIntoQat() this 'leaf' artifact
is marked as QA at the corresponding 'leaf' QAT and to the respective $tableAlias which is used to link hte remaining paths to
the correct table alias.
However, subqueries are not considered in the mergePathIntoQat(), so a subquery QA must be created and added separately to
the lead query $tableAlias'es.
Also the name of the subquery (the alias) needs to be set to the final QA alias name.
Each leaf node of a table path must end in either a direct or a target artifact.
During mergePathIntoQat() this 'leaf' artifact is marked as a QA at the corresponding
'leaf' QAT and to the respective $tableAlias which is used to link paths to the correct
table alias. Subqueries are not considered in the mergePathIntoQat(), so a subquery QA
must be created and added separately to the lead query $tableAlias'es.
Also the name of the subquery (the alias) needs to be set to the final QA alias name.
*/
function createQAForFromClauseSubQuery(query, env)
{
// only subqueries of the FROM clause have a name (which is the alias)
// Only subqueries of the FROM clause have a name (which is the alias)
if(query.op.val === 'query' && query.name.id)
{
// set the QA for the outer ON cond paths and rename the query name itself
// Set the QA for the outer ON cond paths and rename the query name itself
let QA = query._tableAlias._parent.$tableAliases[query.name.id].QA = createQA(env, query);

@@ -219,9 +213,9 @@ incAliasCount(env, QA);

/*
Add an artificial QA to each mixin definition. This QA completes the QAT
Add an artificial QA for each mixin definition. This QA completes the QAT
datastructure that requires a QA at the rootQat before starting the join generation.
This QA is marked as 'mixin' which indicates that the paths of the ON condition must
not receive the usual source and target table alias (which is used for generic associations)
but instead just use the rootQA of the individual ON condition path. These paths are
but instead just use the rootQA of the individual ON condition paths. These paths are
resolved against the FROM clause and must of course be connected to the respective table
aliases
aliases.
*/

@@ -233,3 +227,3 @@ function createQAForMixinAssoc(query, env)

env.lead = query;
// use view as QA origin, don't increment aliasCount
// use view as QA origin
forEachGeneric(query, 'mixin', art => {

@@ -239,3 +233,3 @@ if(!art.QA)

art.QA = createQA(env, art.target._artifact);
art.QA.mixin = true; // <=== this is the marker
art.QA.mixin = true;
}

@@ -252,5 +246,5 @@ });

Rewrite a given path of the native ON condition to TableAlias.ColumnName
and substitute all eventually ocurring foreign key path segments against the respective FK aliases
No flattening of structured leaf types necessary, this is done later in toSQL renderer
and substitute all eventually occurring foreign key path segments against
the respective FK aliases.
No flattening of structured leaf types necessary, this is done in renderer
*/

@@ -296,5 +290,5 @@ function rewriteGenericPaths(pathNode, env)

/*
Logically AND the filter conditions of the first path steps of the FROM clause to
the WHERE condition. If no WHERE is specified, create a new one. This step must be done after running
rewriteGenericPaths because otherwise the filter expressions would be traversed twice.
AND filter conditions of the first path steps of the FROM clause to the WHERE condition.
If WHERE does not exist, create a new one. This step must be done after rewriteGenericPaths()
as the filter expressions would be traversed twice.
*/

@@ -319,3 +313,3 @@ function attachFirstFilterConditions(query)

}
// recurse into sub queries
// Recurse into sub queries
query.queries.map(q => attachFirstFilterConditions(q));

@@ -326,5 +320,6 @@ }

/*
transform a QAT into a JOIN tree
Starting from a root (parentQat) follow all QAT children and in case QAT.origin is an association,
create a new JOIN node using the existing joinTree as LHS and the QAT.QA as RHS.
Transform a QATree into a JOIN tree
Starting from a root (parentQat) follow all QAT children and in
case QAT.origin is an association, create a new JOIN node using
the existing joinTree as LHS and the QAT.QA as RHS.
*/

@@ -339,3 +334,3 @@ function createJoinTree(env, joinTree, parentQat, joinType, qatAttribName, lastAssocQA)

if(isEntityOrView(art)) // check if this pathstep is an entity or view
if(isEntityOrView(art))
{

@@ -347,9 +342,9 @@ if(!childQat.QA)

if(joinTree === undefined) // this is the first artifact in the JOIN tree
if(joinTree === undefined) // This is the first artifact in the JOIN tree
{
joinTree = childQat.QA;
// collect the toplevel filters and add them to the where condition
// Collect the toplevel filters and add them to the where condition
if(childQat._filter)
{
// filter conditions are unique for each JOIN, they don't need to be copied
// Filter conditions are unique for each JOIN, they don't need to be copied
let filter = childQat._filter;

@@ -376,4 +371,4 @@ rewritePathsInExpression(filter, function(pathNode) {

// do not create a JOIN for that assoc if it has no subsequent path steps
// (except for the last path step in the from table path)
// Do not create a JOIN for that assoc if it has no subsequent path steps
// (except for the last path step in the from table path)
if(env.location == 'from' || getChildrenCount(childQat) > 0)

@@ -387,3 +382,3 @@ {

}
// follow the children of this qat to append more JOIN nodes
// Follow the children of this QAT to append more JOIN nodes
joinTree = createJoinTree(env, joinTree, childQat[qatAttribName], joinType, qatAttribName, newAssocLHS);

@@ -423,3 +418,3 @@ }

// return the number of direct children to the given qat accross all filters
// Return the number of direct children to the given qat accross all filters
function getChildrenCount(qat)

@@ -436,3 +431,2 @@ {

// creator methods for QAs
function createJoinQA(joinType, lhs, rhs, assocQAT, assocSourceQA)

@@ -448,9 +442,10 @@ {

// Inject the ON condition of the managed association
if(assocElt._finalType.foreignKeys)
{
/*
get both the source and the target column names for the eq term
for the src side provide a path prefix for all paths that is the assocElement name itself preceded by
Get both the source and the target column names for the EQ term.
For the src side provide a path prefix for all paths that is the assocElement name itself preceded by
the path up to the first lead artifact (usually the entity or view) (or in QAT speak: follow the parent
QATs until a QA has been found)
QATs until a QA has been found).
*/

@@ -464,3 +459,6 @@ let srcPaths = flattenElement(assocElt, true, assocElt.name.element.replace(/\./g, pathDelimiter));

// put all src/tgt path siblings into the eq term and create the proper path objects with the src/tgt table alias path steps in front
/*
Put all src/tgt path siblings into the EQ term and create the proper path objects
with the src/tgt table alias path steps in front.
*/
let args = [];

@@ -473,6 +471,7 @@ for(let i = 0; i < srcPaths.length; i++)

}
// parenthesize each AND term
// Parenthesize each AND term
node.on = [ (args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(a=>[a]) ] } : args[0] ) ];
}
// Inject the ON condition of the unmanaged association
else if (assocElt.onCond || assocElt.on)

@@ -490,6 +489,40 @@ {

if(head.id === '$projection')
throw Error('Following mix-in association "' + assocElt.name.id + '" in defining view is not allowed with ON condition from projection: ' + pathAsStr(pathNode.path, '"'));
throw Error('Following mix-in association "' + assocElt.name.id +
'" in defining view is not allowed with ON condition from projection: ' +
pathAsStr(pathNode.path, '"'));
/*
If all mixin assoc paths would result in the same join node (that is exactly
one shared QAT for all mixin path steps) it would be sufficient to reuse the
definition QA (see createQAForMixinAssoc()) for sharing the table alias.
As mixin assoc paths may have different filter conditions, separate QATs are
created for each distinct filter, resulting in separate JOIN trees requiring
individual table aliases. This also requires separate QAs at the assoc QAT
to hold the individual table aliases (that's why the definition QA is cloned
in mergePathIntoQAT()).
Paths in the ON condition referring to the target side are linked to the
original mixin QA via head._navigation (done by the compiler), which in turn
is childQat._parent (a mixin assoc path step MUST be path root, so _parent
IS the mixin definition. Mixin QATs are created at the mixin definition).
In order to create the correct table alias path, the definition QA must
be replaced with the current childQat.QA (the clone with the correct alias).
The original QA is used as template for its clones and can safely be replaced.
Example:
select from ... mixin { toTgt: association to Tgt on toTgt.elt = elt; }
into { toTgt[f1].field1, toTgt[f2].field2 };
toTgt definition has definition QA, ON cond path 'toTgt' refers to definition QA.
assoc path 'toTgt[f1].' and 'toTgt[f2]' have separate QATs with QA clones.
'toTgt.elt' must now be rendered for each JOIN using the correct QA clone.
*/
if(assocQAT.QA.mixin)
assocQAT._parent.QA = assocQAT.QA;
return constructTableAliasAndTailPath(path);
}
else
else // ON condition of non-mixin association
{

@@ -504,3 +537,3 @@ if(head.id === assocQAT.name.id) // target side

{
// eventually remove $self from path
// eventually remove $self and $projection from path
let isAbsolutePath = head.id === '$self';

@@ -520,3 +553,4 @@ if(isAbsolutePath || head.id === '$projection')

{
throw Error('assocQAT has neither foreign keys nor an on condition:' + assocElt.name.absolute + pathDelimiter + assocElt.name.element);
throw Error('assocQAT has neither foreign keys nor an on condition:' +
assocElt.name.absolute + pathDelimiter + assocElt.name.element);
}

@@ -526,3 +560,3 @@

{
// filter conditions are unique for each JOIN, they don't need to be copied
// Filter conditions are unique for each JOIN, they don't need to be copied
let filter = assocQAT._filter;

@@ -533,3 +567,3 @@ rewritePathsInExpression(filter, function(pathNode) {

// if toplevel ON cond op is AND add filter condition to the args array,
// If toplevel ON cond op is AND add filter condition to the args array,
// create a new toplevel AND op otherwise

@@ -539,5 +573,7 @@ let onCond = (Array.isArray(node.on) ? node.on[0] : node.on);

if(onCond.op.val == 'and')
onCond.args.push( [ filter ] ); // parenthesize filter
// parenthesize filter
onCond.args.push( [ filter ] );
else
node.on = [ { op: { val: 'and' }, args: [ [ onCond ], [ filter ] ] } ]; // parenthesize onCond and filter
// parenthesize onCond and filter
node.on = [ { op: { val: 'and' }, args: [ [ onCond ], [ filter ] ] } ];
}

@@ -549,4 +585,4 @@

/*
A QA (QueryArtifact) is a representative forn a table/view that must appear in the FROM clause
either named directly or indirectly through an association
A QA (QueryArtifact) is a representative for a table/view that must appear
in the FROM clause either named directly or indirectly through an association.
*/

@@ -575,10 +611,9 @@ function createQA(env, artifact, parameters, alias)

/*
recursively walk over expression and replace any found path against a new
path consisting of two path steps.
The first path step is the table alias and the second path step is
the concatenated string of the original path steps. The leaf _artifact
of pathNode is used as the leaf artifact of the new path string.
Recursively walk over expression and replace any found path with a new
path consisting of two path steps. The first path step is the table alias
and the second path step is the concatenated string of the original path steps.
Leaf _artifact of pathNode is used as the leaf artifact of the new path string.
Both the table alias and the original (remaining) path steps are
returned from getTableAliasAndPathSteps()
Both the table alias and the original (remaining) path steps are to be produced
by getTableAliasAndPathSteps().

@@ -627,17 +662,22 @@ tableAlias = [ aliasName, _artifact ]

/*
replace the content of the old node with the new one
Replace the content of the old node with the new one.
If newNode is a join tree (rewritten FROM path), oldPath must be cleared first.
If newNode is a path => oldNode._artifact === newNode._artifact, no need to
exchange _artifact (as non-iterable property it is not assigned).
*/
function replaceNodeContent(oldNode, newNode)
{
Object.keys(oldNode).forEach(k => {
delete oldNode[k] });
delete oldNode._artifact;
delete oldNode._status;
// If newNode is a join tree, throw away old path content
if(newNode.op) {
Object.keys(oldNode).forEach(k => {
delete oldNode[k] });
delete oldNode._artifact;
}
Object.assign(oldNode, newNode);
}
/* collect all of paths to all leafs for a given element
respecting the src or the target side of the ON condition
return an array of column names and it's leaf element
/*
Collect all of paths to all leafs for a given element
respecting the src or the target side of the ON condition.
Return an array of column names and it's leaf element.
*/

@@ -687,3 +727,3 @@ function flattenElement(element, srcSide, prefix)

/*
construct both the TA path step and the path tail for a given path array from the AST
Construct both the TA path step and the path tail for a given AST path array
*/

@@ -695,3 +735,3 @@ function constructTableAliasAndTailPath(path)

// first path step is table alias, use it and pop it off
// First path step is table alias, use and pop it off
if(head._navigation.QA)

@@ -705,4 +745,4 @@ path = tail;

/*
Translate ON cond paths and substitute FK aliases
return array of [ [pathName, _artifact] ]
Translate ON cond paths and substitute FK aliases
return array of [ [pathName, _artifact] ]
*/

@@ -717,3 +757,3 @@ function translateONCondPath(path, prefix)

}
// artifact is not needed, return value must fit in rewritePath expectations (array of path objects with id }
// Artifact is not needed, return value must fit in rewritePath expectations (array of path objects with id }
return [ { id: (prefix ? prefix + fkPrefix : fkPrefix) } ];

@@ -723,5 +763,6 @@ }

/*
munch path steps and append them to a path string until an assoc step is found. The assoc path step is also
appended to the path string. If no assoc path step has occured, all path steps are added to the path string
and tail is empty.
Munch path steps and append them to a path string until an
assoc step is found. The assoc path step is also appended
to the path string. If no assoc path step has occured, all
path steps are added to the path string and tail is empty.

@@ -745,6 +786,4 @@ Return assocPathStep, the remaining tail path and the path string

Substitute the n first path steps of a given path against a FK alias name.
Resolve a foreign key of an (managaged) association by following the n first path steps of a given path.
The longest path matches:
Resolve a foreign key of a managaged association by following the n first
path steps. Longest path matches:
Example: fk tuple { a.b, a.b.c, a.b.e },

@@ -755,3 +794,3 @@ path: a.b.c.d.e.f: FK a.b.c is found, even if FK a.b is one level higher in the prefix tree.

Return remaining tail path and the path string
Return remaining tail path and the path string.
*/

@@ -787,3 +826,3 @@

let tail = path.slice(path.indexOf(fkPs)+1);
// if foreign key is an association itself, apply substituteFKAliasForPath on tail
// If foreign key is an association itself, apply substituteFKAliasForPath on tail
if(fk && fk.targetElement._artifact.target && tail.length)

@@ -807,3 +846,4 @@ return substituteFKAliasForPath(fk.targetElement, tail, pathStr);

if(pathDict._artifact._finalType.elements)
signal(error`Only scalar types allowed in this location of a query: ' + pathAsStr(pathDict.path, "'")`);
signal(error`Only scalar types allowed in this location of a query: ' +
pathAsStr(pathDict.path, "'")`);

@@ -827,3 +867,4 @@ let [head, ...tail] = path;

if(la1 && !ps._artifact._finalType.fkPathPrefixTree.children[la1.id])
signal(error`Pathstep ' + la1.id + ' is not foreign key of association ' + ps.id + ' in ON condition path: ' + pathAsStr(pathDict.path)`);
signal(error`Pathstep ' + la1.id + ' is not foreign key of association ' +
ps.id + ' in ON condition path: ' + pathAsStr(pathDict.path)`);
}

@@ -836,3 +877,4 @@ }

if(pathDict.path[0].id === '$projection')
signal(error`'$projection' outside of ON conditions not supported: " + pathAsStr(pathDict.path)`);
signal(error`'$projection' outside of ON conditions not supported: " +
pathAsStr(pathDict.path)`);
}

@@ -842,18 +884,19 @@ }

/*
Create path prefix trees and merge paths into the trees depending on the path location.
There are three prefix trees for FROM table paths, ON conditions (of either JOINs in FROM clause or
of ad-hoc associations) and all other paths. It is not the job of this transformer to semantically
check for illegal association path steps in the various clauses of the query.
Create path prefix trees and merge paths into the trees depending on the path location.
There are prefix trees for FROM table paths and all other paths. Paths of ON conditions
(of either JOINs in FROM clause or of mixin associations) are not added to the QATree,
as no associations can be followed in these paths. It is not the job of this transformer
to semantically check for illegal association path steps in the various clauses of the query.
All prefix trees are located underneath the $tableAlias structure and are distinguished
by their attribute $qat, $fqat and $nqat. Each path step appears exactly once for a given filter condition
in the prefix tree and has a link to it's definition (origin). The default filter is an empty string ''.
All prefix trees are put underneath the $tableAlias structure with attribute $qat or $fqat.
Each path step appears exactly once for a given filter condition in the prefix tree and
has a link to it's definition (origin). The default filter is an empty string ''.
A special note on paths in filter conditions. Filter paths are treated like postfix
paths to an association path step, meaning, they are inserted into the assoc's $qat or $fqat depending on where
the association was traversed. As HANA CDS doesn't allow to traverse assocs in filter path, this is checked
in flyTrap above.
A node in the path prefix tree is abbreviated as QAT (which stands for query association tree, a term
originating from way back in time).
A special note on paths in filter conditions. Filter paths are treated like postfix
paths to an association path step, meaning, they are inserted into the assoc's $qat or $fqat
depending on where the association was traversed.
As HANA CDS doesn't allow to traverse assocs in filter paths, this is checked in flyTrap above.
A node in the path prefix tree is abbreviated as QAT (which stands for Query Association Tree,
a term originating from way back in time).
*/

@@ -871,3 +914,3 @@ function mergePathIntoQAT(pathDict, env)

if(env.location == 'onCondFrom')
return; //qatChildrenName = '$nqat'
return;

@@ -883,3 +926,4 @@ let [head, ...tail] = path;

{
// speciality for OrderBy: If path has no _navigation don't merge it. Path is alias to select item expression
// speciality for OrderBy: If path has no _navigation don't merge it.
// Path is alias to select item expression
if(env.location === 'OrderBy')

@@ -898,16 +942,17 @@ return;

}
// all other paths have a _navigation attribute
// All other paths have a _navigation attribute
else if(head._navigation)
{
// Always start with QAT merge at $tableAlias, even if path doesn't start there:
// First identify $tableAlias (must be either head or head's parent)
// The resolver sets a _navigation at the very first path step that either points to
// $tableAlias or to a top level element from $combined which itself parent's to $tableAlias).
/*
Always start with QAT merge at $tableAlias, even if path doesn't start there.
First identify $tableAlias (must be either head or head's parent) The resolver
sets a _navigation at the very first path step that either points to $tableAlias
or to a top level element from $combined which itself parent's to $tableAlias).
*/
if(head._navigation.kind == '$navElement')
{
qatParent = head._navigation._parent;
tail = path; // start with the full path (no table alias prefix)
tail = path; // Start with the full path (no table alias prefix)
}
else if(head._navigation.kind == 'element') // this is a mixin assoc
else if(head._navigation.kind == 'element') // This is a mixin assoc
{

@@ -917,3 +962,3 @@ qatParent = head._navigation;

}
else // head is a table alias already
else // Head is a table alias already
{

@@ -929,10 +974,14 @@ qatParent = head._navigation;

// create the very first QAT if it doesn't exist yet (no filter condition for table alias prefix)
// Create the very first QAT if it doesn't exist
// (filter condition for table alias prefix not allowed)
let qatChildren = createQATChildren(qatParent);
let qat = undefined;
for(let pathStep of tail)
{
// if the current path step has not yet been inserted into the child list of
// the parent QAT, create a new QAT (linkToOrigin) and a dictionary for the
// subsequent path steps (a separate one for each filter condition).
/*
If the current path step has not yet been inserted into the list of children at
the parent QAT, create a new QAT (linkToOrigin) and a dictionary for subsequent
path steps (a separate one for each filter condition).
*/
let filterStr = '';

@@ -946,6 +995,11 @@ if(pathStep.where)

qat = linkToOrigin(pathStep._artifact, pathStep.id, qatParent, undefined, pathStep.location);
// this is for mixin associations: if a mixin assoc is traversed, it's definition already has a QA, attach it to the qat
// to make sure that the same alias is used.
/*
If qat.origin._artifact has a QA, it must be a mixin association
(No other QA's have been created so far). Clone new QA from this
template to have space for table aliases (if mixin assoc is
followed with different filter conditions).
*/
if(qat.origin._artifact.QA)
qat.QA = qat.origin._artifact.QA;
qat.QA = clone(qat.origin._artifact.QA);
if(pathStep.where)

@@ -957,16 +1011,17 @@ setProp(qat, '_filter', pathStep.where);

qatChildren = createQATChildren(qat);
qatParent = qat; // now the current qat becomes parent to the next level of children
qatParent = qat; // Current qat becomes parent to the next level of children
setProp( pathStep, '_navigation', qat );
}
/* If this path terminates on either an entity or an association
(from clause, published Ad-Hoc Assocs), create a QA and attach it
to the (leaf) QAT and to the rootQAT (which is the tableAlias).
This QA will later serve as the initial 'lastAssocQA'
to all other join relevant paths of the query that originate from this alias.
Also this is the only place where the from table path alias is accessible.
*/
if(!qat)
throw Error('No leaf qat for head: ' + head + ' tail: ' + pathAsStr(tail, '"') + ' produced');
/*
If path terminates on an entity or an association (from clause,
published Ad-Hoc Assocs), attach QA to the (leaf) QAT and to the
rootQAT (which is the tableAlias).
This QA will later serve as the initial 'lastAssocQA' for all other
join relevant paths that originate from this alias. Also this is the
only place where the original FROM alias is available.
*/
let art = qat.origin._artifact;

@@ -981,5 +1036,6 @@ if(!modelUtils.isArtifact(art))

{
// if rootQat ($tableAlias) already has a QA, reuse it, otherwise create a new one
// If rootQat ($tableAlias) already has a QA, reuse it, create a new one otherwise.
if(!rootQat.QA)
{
// Use the original FROM alias if available!
let alias = pathDict.name ? pathDict.name.id : undefined;

@@ -990,3 +1046,3 @@ rootQat.QA = qat.QA = createQA(env, art, pathDict._parameters, alias);

// return or create a new children dictionary for a given QAT
// Return or create a new children dictionary for a given QAT
// Children are grouped under the filter condition that precedes them.

@@ -1001,3 +1057,3 @@ function createQATChildren(parentQat)

// crawl all relevant sections of the AST for paths
// Crawl all relevant sections of the query AST for paths
function walkQuery(query, env)

@@ -1019,4 +1075,2 @@ {

// maybe not very sharp: walkover=select will unlock
// all generic paths
env.location = 'select';

@@ -1042,8 +1096,6 @@ if(env.walkover[env.location])

walk(query.offset, env);
// TODO: which clauses are missing
// union, intersect, except?
}
}
// finally apply walkQuery on all sub queries
// walk all subqueries of this query
query.queries.map(q => walkQuery(q, env));

@@ -1091,3 +1143,3 @@

{
// in some expressions queries can occur, do not follow them as they
// In some expressions queries can occur, do not follow them as they
// are walked as member of the queries array

@@ -1101,3 +1153,3 @@ if(!env || !node || (node && node.op && node.op.val == 'query'))

// ask for Array before typeof object (which would also be true for Array)
// Ask for Array before typeof object (which would also be true for Array)
if(Array.isArray(node))

@@ -1114,3 +1166,3 @@ node.map(n => walk(n, env));

let path = node['path'];
// don't bite into paths that that have no artifact (function calls etc)
// Ignore paths that that have no artifact (function calls etc)
if(path && path[0]._artifact)

@@ -1131,4 +1183,5 @@ {

{
// walk over all filter expressions (not JOIN relevant,
// cannot be detected in generic walk
// Walk over all filter expressions (not JOIN relevant,
// cannot be detected in generic walk. Store path step
// to which this filter was attached to in filterEnt.pathStep
path.filter(pathStep=>pathStep.where).forEach(pathStep => {

@@ -1173,3 +1226,3 @@ filterEnv.pathStep = pathStep;

let props = Object.getOwnPropertyNames(obj); // we clone only own properties, not inherited one's
let props = Object.getOwnPropertyNames(obj); // clone own properties only, not inherited ones
for (let p of props) {

@@ -1187,5 +1240,5 @@ let pd = Object.getOwnPropertyDescriptor(obj, p);

}
} // end of translateAssocsToJoins
}
module.exports = { translateAssocsToJoins };
{
"name": "@sap/cds-compiler",
"version": "1.1.1",
"version": "1.1.3",
"dependencies": {

@@ -5,0 +5,0 @@ "ajv": {

@@ -1,1 +0,1 @@

{"bin":{"cdsc":"bin/cdsc.js"},"bundleDependencies":false,"dependencies":{"ajv":"6.1.1","antlr4":"4.7.1","commander":"2.14.0","fs-extra":"5.0.0","resolve":"1.5.0","sax":"1.2.4"},"deprecated":false,"description":"Standard-Feature-Set Vanilla-CDS in Product Quality","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","repository":{"type":"git","url":"git@github.wdf.sap.corp/CDS/cds-compiler.git"},"version":"1.1.1","license":"SEE LICENSE IN developer-license-3.1.txt"}
{"bin":{"cdsc":"bin/cdsc.js"},"bundleDependencies":false,"dependencies":{"ajv":"6.1.1","antlr4":"4.7.1","commander":"2.14.0","fs-extra":"5.0.0","resolve":"1.5.0","sax":"1.2.4"},"deprecated":false,"description":"Standard-Feature-Set Vanilla-CDS in Product Quality","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","repository":{"type":"git","url":"git@github.wdf.sap.corp/CDS/cds-compiler.git"},"version":"1.1.3","license":"SEE LICENSE IN developer-license-3.1.txt"}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc