@sap/cds 1.15.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/{developer-license-3.1.txt → LICENSE} +37 -35
- package/_hdbext/README.md +373 -0
- package/_hdbext/index.js +4 -0
- package/_hdbext/lib/client-factory.js +62 -0
- package/_hdbext/lib/client-session.js +96 -0
- package/_hdbext/lib/conn-options.js +84 -0
- package/_hdbext/lib/constants.js +79 -0
- package/_hdbext/lib/internal-constants.js +7 -0
- package/_hdbext/lib/middleware.js +46 -0
- package/_hdbext/lib/pool.js +236 -0
- package/_hdbext/lib/safe-sql.js +17 -0
- package/_hdbext/lib/sql-injection-utils.js +149 -0
- package/cds-queries-geo.js +347 -371
- package/cds-queries.js +2692 -2229
- package/cds.js +111 -104
- package/exprs.js +118 -107
- package/manager.js +696 -614
- package/metadata.js +604 -542
- package/npm-shrinkwrap.json +175 -0
- package/package.json +40 -1
- package/transaction.js +45 -51
- package/util/Queue.js +32 -30
- package/utils.js +182 -159
- package/xsjs-cds.js +231 -221
- package/.project +0 -11
- package/TUTORIAL.md +0 -1236
- package/dependencies +0 -56
- package/sapcds.manifest +0 -1747
package/metadata.js
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
const async = require('async');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var SqlQuery = queries.Query;
|
|
3
|
+
const transaction = require('./transaction');
|
|
4
|
+
const utils = require('./utils');
|
|
5
|
+
const Queue = require('./util/Queue');
|
|
6
|
+
const queries = require('./cds-queries');
|
|
7
|
+
const SqlQuery = queries.Query;
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
const logger = utils.logger;
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
// / entity management ///////////////////////////////////////////////////////
|
|
14
13
|
|
|
15
14
|
// known resolved entities
|
|
16
|
-
|
|
15
|
+
let knownEntities = {};
|
|
17
16
|
|
|
18
17
|
// pending callbacks for async getEntity()
|
|
19
|
-
|
|
18
|
+
const importNotifications = {};
|
|
20
19
|
|
|
21
20
|
// metadata cache
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const sqlMetadata = {};
|
|
22
|
+
const cdsMetadata = {};
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
/* import list of entities
|
|
@@ -37,298 +36,327 @@ var cdsMetadata = {};
|
|
|
37
36
|
*/
|
|
38
37
|
|
|
39
38
|
// central import request queue for serializing imports
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
exports._import = function (refs, opts, callback) {
|
|
43
|
-
// work in progress: entities and dependencies to import
|
|
44
|
-
var pool = {};
|
|
45
|
-
|
|
46
|
-
// request import of dependent associated entities
|
|
47
|
-
function addDepImports(entity) {
|
|
48
|
-
utils.forStruct(entity.$_mapping, {
|
|
49
|
-
$association: function(s, f, p) {
|
|
50
|
-
var name = s[f].$association.$entity;
|
|
51
|
-
var schema = opts.$schema || s[f].$association.$schema;
|
|
52
|
-
todo.push({ $entity: name, $schema: schema,
|
|
53
|
-
$options: { $auto: true } });
|
|
54
|
-
logger.debug("node-cds import: added dependency " +
|
|
55
|
-
entity.$_metadata.entityName + " -> " + name);
|
|
56
|
-
if (s[f].$association.$viaEntity) {
|
|
57
|
-
name = s[f].$association.$viaEntity;
|
|
58
|
-
todo.push({$entity: name, $schema: schema,
|
|
59
|
-
$options: {$unmanaged: true, $auto: true}});
|
|
60
|
-
logger.debug("node-cds import: added dependency " +
|
|
61
|
-
entity.$_metadata.entityName + " -> " + name);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
39
|
+
const importQueue = new Queue.Queue();
|
|
66
40
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
var tableName = ref.$table || cdsFullname;
|
|
71
|
-
var schemaName = ref.$schema || ""; // '"' + ref.$schema + '".' : "";
|
|
72
|
-
var fields = ref.$fields || {};
|
|
73
|
-
var options = ref.$options || {};
|
|
74
|
-
|
|
75
|
-
if (!entityName)
|
|
76
|
-
return callback("Missing entity name");
|
|
77
|
-
if (!tableName)
|
|
78
|
-
return callback("Unknown entity table");
|
|
79
|
-
|
|
80
|
-
if (ref.$table && !ref.$entity)
|
|
81
|
-
options.$noCds = true;
|
|
82
|
-
if (ref.$unmanaged)
|
|
83
|
-
options.$unmanaged = true;
|
|
84
|
-
|
|
85
|
-
// retrieve metadata and build entity
|
|
86
|
-
logger.debug("node-cds import: query metadata for " + schemaName + "." + tableName);
|
|
87
|
-
getSqlMetadata(tableName, schemaName, function(err, sqlMetadata) {
|
|
88
|
-
if (err)
|
|
89
|
-
return callback("Error importing " + cdsFullname + ": " + err);
|
|
90
|
-
getCdsMetadata(cdsFullname, schemaName, options, function(err, cdsMetadata) {
|
|
91
|
-
if (err)
|
|
92
|
-
return callback("Error importing " + cdsFullname + ": " + err);
|
|
93
|
-
if (entityName in pool)
|
|
94
|
-
throw new Error("*** ASSERT FAIL *** conflicting entity in pool");
|
|
95
|
-
var entity = makeEntity(entityName, tableName, schemaName, fields,
|
|
96
|
-
sqlMetadata, cdsMetadata, options);
|
|
97
|
-
if (typeof entity === "string")
|
|
98
|
-
return callback(entity); // actually, we got an error
|
|
99
|
-
pool[entityName] = entity;
|
|
100
|
-
logger.debug("node-cds import: added " + entityName + " to wip pool");
|
|
101
|
-
addDepImports(entity); // recursively import target entities
|
|
102
|
-
return callback(null);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
}
|
|
41
|
+
exports._import = function(refs, opts, callback) {
|
|
42
|
+
// work in progress: entities and dependencies to import
|
|
43
|
+
const pool = {};
|
|
106
44
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
45
|
+
// request import of dependent associated entities
|
|
46
|
+
function addDepImports(entity) {
|
|
47
|
+
utils.forStruct(entity.$_mapping, {
|
|
48
|
+
$association: function(s, f, p) {
|
|
49
|
+
let name = s[f].$association.$entity;
|
|
50
|
+
const schema = opts.$schema || s[f].$association.$schema;
|
|
51
|
+
todo.push({$entity: name, $schema: schema,
|
|
52
|
+
$options: {$auto: true}});
|
|
53
|
+
logger.debug(`node-cds import: added dependency ${
|
|
54
|
+
entity.$_metadata.entityName } -> ${ name}`);
|
|
55
|
+
if (s[f].$association.$viaEntity) {
|
|
56
|
+
name = s[f].$association.$viaEntity;
|
|
57
|
+
todo.push({$entity: name, $schema: schema,
|
|
58
|
+
$options: {$unmanaged: true, $auto: true}});
|
|
59
|
+
logger.debug(`node-cds import: added dependency ${
|
|
60
|
+
entity.$_metadata.entityName } -> ${ name}`);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// main metadata query function
|
|
67
|
+
function query(entityName, ref, callback) {
|
|
68
|
+
const cdsFullname = ref.$entity;
|
|
69
|
+
const tableName = ref.$table || cdsFullname;
|
|
70
|
+
const schemaName = ref.$schema || ''; // '"' + ref.$schema + '".' : "";
|
|
71
|
+
const fields = ref.$fields || {};
|
|
72
|
+
const options = ref.$options || {};
|
|
73
|
+
|
|
74
|
+
if (!entityName) {
|
|
75
|
+
return callback('Missing entity name');
|
|
115
76
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
async.whilst(pending, loop, callback);
|
|
77
|
+
if (!tableName) {
|
|
78
|
+
return callback('Unknown entity table');
|
|
119
79
|
}
|
|
120
80
|
|
|
121
|
-
|
|
122
|
-
|
|
81
|
+
if (ref.$table && !ref.$entity) {
|
|
82
|
+
options.$noCds = true;
|
|
83
|
+
}
|
|
84
|
+
if (ref.$unmanaged) {
|
|
85
|
+
options.$unmanaged = true;
|
|
123
86
|
}
|
|
124
87
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
pool[entityName] = previously; // present to callback
|
|
138
|
-
var options = item.$options || {};
|
|
139
|
-
if (!options.$auto) {
|
|
140
|
-
updateEntity(previously, item.$fields || {}, options);
|
|
141
|
-
addDepImports(previously);
|
|
142
|
-
}
|
|
143
|
-
return callback(null);
|
|
88
|
+
// retrieve metadata and build entity
|
|
89
|
+
logger.debug(`node-cds import: query metadata for ${ schemaName }.${ tableName}`);
|
|
90
|
+
getSqlMetadata(tableName, schemaName, function(err, sqlMetadata) {
|
|
91
|
+
if (err) {
|
|
92
|
+
return callback(`Error importing ${ cdsFullname }: ${ err}`);
|
|
93
|
+
}
|
|
94
|
+
getCdsMetadata(cdsFullname, schemaName, options, function(err, cdsMetadata) {
|
|
95
|
+
if (err) {
|
|
96
|
+
return callback(`Error importing ${ cdsFullname }: ${ err}`);
|
|
97
|
+
}
|
|
98
|
+
if (entityName in pool) {
|
|
99
|
+
throw new Error('*** ASSERT FAIL *** conflicting entity in pool');
|
|
144
100
|
}
|
|
101
|
+
const entity = makeEntity(entityName, tableName, schemaName, fields,
|
|
102
|
+
sqlMetadata, cdsMetadata, options);
|
|
103
|
+
if (typeof entity === 'string') {
|
|
104
|
+
return callback(entity);
|
|
105
|
+
} // actually, we got an error
|
|
106
|
+
pool[entityName] = entity;
|
|
107
|
+
logger.debug(`node-cds import: added ${ entityName } to wip pool`);
|
|
108
|
+
addDepImports(entity); // recursively import target entities
|
|
109
|
+
return callback(null);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// top level: request queue handling
|
|
115
|
+
const todo = [];
|
|
116
|
+
for (const i in refs) {
|
|
117
|
+
const r = utils.shallowCopy(refs[i]);
|
|
118
|
+
// merge in global options
|
|
119
|
+
for (const p in opts) {
|
|
120
|
+
if (!(p in r)) {
|
|
121
|
+
r[p] = opts[p];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
todo.push(r);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function start(callback) {
|
|
128
|
+
async.whilst(pending, loop, callback);
|
|
129
|
+
}
|
|
145
130
|
|
|
146
|
-
|
|
131
|
+
function pending() {
|
|
132
|
+
return todo.length > 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function loop(callback) {
|
|
136
|
+
const item = todo.shift();
|
|
137
|
+
if (!item) {
|
|
138
|
+
throw new Error('*** ASSERT FAIL *** empty todo list');
|
|
139
|
+
}
|
|
140
|
+
const entityName = item.$name || item.$entity || item.$table;
|
|
141
|
+
logger.debug(`node-cds import loop: processing ${ entityName}`);
|
|
142
|
+
|
|
143
|
+
// choke recursive imports
|
|
144
|
+
const previously = pool[entityName] || knownEntities[entityName];
|
|
145
|
+
if (previously) {
|
|
146
|
+
logger.debug(`node-cds import: ${ entityName } already known`);
|
|
147
|
+
if (!(entityName in pool)) {
|
|
148
|
+
pool[entityName] = previously;
|
|
149
|
+
} // present to callback
|
|
150
|
+
const options = item.$options || {};
|
|
151
|
+
if (!options.$auto) {
|
|
152
|
+
updateEntity(previously, item.$fields || {}, options);
|
|
153
|
+
addDepImports(previously);
|
|
154
|
+
}
|
|
155
|
+
return callback(null);
|
|
147
156
|
}
|
|
148
157
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return callback(err, []);
|
|
158
|
+
query(entityName, item, callback);
|
|
159
|
+
}
|
|
152
160
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
registerEntity(pool[i]);
|
|
158
|
-
}
|
|
159
|
-
logger.debug("node-cds import: " + pool[i].$_metadata.entityName + " imported");
|
|
160
|
-
}
|
|
161
|
+
function done(err) {
|
|
162
|
+
if (err) {
|
|
163
|
+
return callback(err, []);
|
|
164
|
+
}
|
|
161
165
|
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
// add XS interface and wire up associations in wip skeletons, add to cache
|
|
167
|
+
for (const i in pool) {
|
|
168
|
+
if (!pool[i].$_metadata.isResolved) {
|
|
169
|
+
resolveEntity(pool[i], pool);
|
|
170
|
+
registerEntity(pool[i]);
|
|
171
|
+
}
|
|
172
|
+
logger.debug(`node-cds import: ${ pool[i].$_metadata.entityName } imported`);
|
|
164
173
|
}
|
|
165
174
|
|
|
166
|
-
|
|
175
|
+
// return result via wip pool
|
|
176
|
+
return callback(null, pool);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
importQueue.push(start, done);
|
|
167
180
|
};
|
|
168
181
|
|
|
169
182
|
|
|
170
183
|
// trigger import of all associated entities
|
|
171
|
-
function makeEntity(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
184
|
+
function makeEntity(
|
|
185
|
+
entityName, tableName, schemaName, fields, sqlMetadata, cdsMetadata, options,
|
|
186
|
+
) {
|
|
187
|
+
// prepare field mapping
|
|
188
|
+
const sqlMapping = buildSqlMapping(sqlMetadata);
|
|
189
|
+
const cdsMapping = buildCdsMapping(cdsMetadata);
|
|
190
|
+
let mapping = mergeMapping(sqlMapping, cdsMapping);
|
|
191
|
+
mapping = mergeMapping(mapping, fields);
|
|
192
|
+
const check = checkMapping(mapping);
|
|
193
|
+
if (check) {
|
|
194
|
+
return check;
|
|
195
|
+
} // found error
|
|
196
|
+
|
|
197
|
+
// assemble entity metadata
|
|
198
|
+
const metadata = {
|
|
199
|
+
entityName: entityName,
|
|
200
|
+
tableName: tableName,
|
|
201
|
+
schemaName: schemaName,
|
|
202
|
+
sqlMetadata: sqlMetadata,
|
|
203
|
+
cdsMetadata: cdsMetadata,
|
|
204
|
+
isUnmanaged: options.$unmanaged || false,
|
|
205
|
+
isAutoImport: options.$auto || false,
|
|
206
|
+
isResolved: false, // true iff mapping contains associated entiy objects
|
|
207
|
+
};
|
|
208
|
+
updateMetadata(metadata, mapping);
|
|
209
|
+
|
|
210
|
+
// build and register unresolved entity object
|
|
211
|
+
const entity = {
|
|
212
|
+
$_metadata: metadata,
|
|
213
|
+
$_mapping: mapping,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// verify entity (in particular, mapping)
|
|
217
|
+
const e = verifyEntity(entity);
|
|
218
|
+
if (e) {
|
|
219
|
+
return e;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// add query interface
|
|
223
|
+
SqlQuery.addExpressionFunctions(entity, entity);
|
|
224
|
+
entity.$query = function(client) {
|
|
225
|
+
const param = {};
|
|
226
|
+
param['t0'] = {entity: this};
|
|
227
|
+
return new SqlQuery(client, param);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
entity.$ref = function(id) {
|
|
231
|
+
return new queries.Ref(this, id);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
entity.$from = function(id) {
|
|
235
|
+
const param = {};
|
|
236
|
+
const e = this;
|
|
237
|
+
param[id] = {entity: e};
|
|
238
|
+
return new SqlQuery(null, param);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return entity;
|
|
224
242
|
}
|
|
225
243
|
|
|
226
244
|
// check various entity properties
|
|
227
245
|
function verifyEntity(entity) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (!assoc.$target)
|
|
236
|
-
error = "Error: missing $target in viaEntity association";
|
|
237
|
-
}
|
|
246
|
+
let error = null;
|
|
247
|
+
utils.forStruct(entity.$_mapping, {
|
|
248
|
+
$association: function(s, f, p) {
|
|
249
|
+
const assoc = s[f].$association;
|
|
250
|
+
if (assoc.$viaEntity) {
|
|
251
|
+
if (!assoc.$source) {
|
|
252
|
+
error = 'Error: missing $source in viaEntity association';
|
|
238
253
|
}
|
|
239
|
-
|
|
240
|
-
|
|
254
|
+
if (!assoc.$target) {
|
|
255
|
+
error = 'Error: missing $target in viaEntity association';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
return error;
|
|
241
261
|
}
|
|
242
262
|
|
|
243
263
|
// update auto-imported entity with new field data
|
|
244
264
|
function updateEntity(entity, fields, options) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
265
|
+
const entityName = entity.$_metadata.entityName;
|
|
266
|
+
logger.debug(`node-cds import: updating entity ${ entityName}`);
|
|
267
|
+
delete knownEntities[entityName]; // remove temporarily, still in wip pool
|
|
268
|
+
entity.$_mapping = mergeMapping(entity.$_mapping, fields);
|
|
269
|
+
entity.$_metadata.isResolved = false;
|
|
270
|
+
updateMetadata(entity.$_metadata, entity.$_mapping);
|
|
271
|
+
if (!options.$auto) {
|
|
272
|
+
entity.$_metadata.isAutoImport = false;
|
|
273
|
+
}
|
|
253
274
|
}
|
|
254
275
|
|
|
255
276
|
// update metadata information based on new mapping (in-place update)
|
|
256
277
|
function updateMetadata(metadata, mapping) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
const keys = {}; let hasKeys = false;
|
|
279
|
+
utils.forStruct(mapping, {
|
|
280
|
+
$key: function(s, f, p) {
|
|
281
|
+
if (s[f].$key) {
|
|
282
|
+
const column = s[f].$column;
|
|
283
|
+
keys[p + f] = {
|
|
284
|
+
$seq: s[f].$key, // metadata.sqlMetadata[column].$key,
|
|
285
|
+
$type: metadata.sqlMetadata[column].$type,
|
|
286
|
+
};
|
|
287
|
+
hasKeys = true;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
// need key for managing instances
|
|
292
|
+
if (!hasKeys && !metadata.isUnmanaged) {
|
|
293
|
+
throw new Error(`no key defined: ${ metadata.entityName}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const revMapping = {};
|
|
297
|
+
utils.forStruct(mapping, {
|
|
298
|
+
$column: function(s, f, p) {
|
|
299
|
+
revMapping[s[f].$column] = p + f;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
metadata.keyFields = keys;
|
|
304
|
+
metadata.revMapping = revMapping;
|
|
305
|
+
// metadata.secondaryIndexes = [];
|
|
283
306
|
}
|
|
284
307
|
|
|
285
308
|
// resolve entity name references by entity classes
|
|
286
309
|
function resolveEntity(entity, pool) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
310
|
+
logger.debug(`node-cds import: resolving ${ entity.$_metadata.entityName}`);
|
|
311
|
+
if (entity.$_metadata.isResolved) {
|
|
312
|
+
throw new Error(`*** ASSERT FAIL *** resolving resolved entity: ${
|
|
313
|
+
entity.$_metadata.entityName}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function getByName(name) {
|
|
317
|
+
const e = pool[name] || knownEntities[name];
|
|
318
|
+
if (!e) {
|
|
319
|
+
throw new Error(`*** ASSERT FAIL *** missing entity: ${ name}`);
|
|
297
320
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
321
|
+
return e;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
utils.forStruct(entity.$_mapping, {
|
|
325
|
+
$association: function(s, f, p) {
|
|
326
|
+
let name = s[f].$association.$entity;
|
|
327
|
+
s[f].$association.$class = getByName(name);
|
|
328
|
+
name = s[f].$association.$viaEntity;
|
|
329
|
+
if (name) {
|
|
330
|
+
s[f].$association.$viaClass = getByName(name);
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
entity.$_metadata.isResolved = true;
|
|
309
335
|
};
|
|
310
336
|
|
|
311
337
|
|
|
312
|
-
|
|
338
|
+
// / other management function ///////////////////////////////////////
|
|
313
339
|
|
|
314
340
|
// return previously imported entity, or save callback if not imported yet
|
|
315
341
|
exports.getEntity = function(name, callback) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
importNotifications[name]
|
|
342
|
+
logger.debug(`node-cds import: getting entity ${ name}`);
|
|
343
|
+
if (name in knownEntities) {
|
|
344
|
+
return callback(null, knownEntities[name]);
|
|
345
|
+
}
|
|
346
|
+
logger.debug(`node-cds import: getEntity waiting for entity ${ name}`);
|
|
347
|
+
if (!(name in importNotifications)) {
|
|
348
|
+
importNotifications[name] = [];
|
|
349
|
+
}
|
|
350
|
+
importNotifications[name].push(callback);
|
|
323
351
|
};
|
|
324
352
|
|
|
325
353
|
// sync version (used by metadata import)
|
|
326
|
-
exports.getEntitySync = function
|
|
327
|
-
|
|
354
|
+
exports.getEntitySync = function(name) {
|
|
355
|
+
return knownEntities[name] || null;
|
|
328
356
|
};
|
|
329
357
|
|
|
330
358
|
exports.getKnownEntities = function() {
|
|
331
|
-
|
|
359
|
+
return knownEntities;
|
|
332
360
|
};
|
|
333
361
|
|
|
334
362
|
|
|
@@ -337,366 +365,400 @@ exports.getKnownEntities = function() {
|
|
|
337
365
|
// For parallel imports (e.g., triggered by auto-imports), the returned entity
|
|
338
366
|
// object must supercede the original object passed to registerEntity.
|
|
339
367
|
function registerEntity(entity) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
368
|
+
const name = entity.$_metadata.entityName;
|
|
369
|
+
if (name in knownEntities) {
|
|
370
|
+
throw new Error(`*** ASSERT FAIL *** trying to register known entity ${ name}`);
|
|
371
|
+
}
|
|
372
|
+
logger.debug(`node-cds import: registering entity ${ name}`);
|
|
373
|
+
|
|
374
|
+
knownEntities[name] = entity;
|
|
375
|
+
|
|
376
|
+
// check for pending requests
|
|
377
|
+
if (name in importNotifications) {
|
|
378
|
+
const notify = importNotifications[name];
|
|
379
|
+
for (const i in notify) {
|
|
380
|
+
(notify[i])(null, entity);
|
|
353
381
|
}
|
|
382
|
+
delete importNotifications[name];
|
|
383
|
+
}
|
|
354
384
|
};
|
|
355
385
|
|
|
356
386
|
|
|
357
|
-
|
|
387
|
+
// / support functions ////////////////////////////////////////////////////////////
|
|
358
388
|
|
|
359
389
|
// compute dependency graph for entity that shows
|
|
360
390
|
// (1) all (non-cyclic) associations,
|
|
361
391
|
// (2) all cyclic associations, and
|
|
362
392
|
// (3) the projection for $query() that covers all non-recursive associations
|
|
363
|
-
exports.computeRelations = function
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
393
|
+
exports.computeRelations = function(entity) {
|
|
394
|
+
const entityName = entity.$_metadata.entityName;
|
|
395
|
+
const projection = {}; const assocs = []; const cycles = [];
|
|
396
|
+
|
|
397
|
+
const _build = function(entity, prefix, seen) {
|
|
398
|
+
utils.setPropPath(projection, `${prefix }$all`, true);
|
|
399
|
+
utils.forStruct(entity.$_mapping, {
|
|
400
|
+
$association: function(m, f, p) {
|
|
401
|
+
const target = m[f].$association.$entity;
|
|
402
|
+
const targetEntity = m[f].$association.$class;
|
|
403
|
+
if (typeof targetEntity === 'undefined') {
|
|
404
|
+
throw new Error('*** ASSERT FAIL *** missing target entity');
|
|
405
|
+
}
|
|
406
|
+
const isToMany = utils.isToMany(m[f].$association);
|
|
407
|
+
const isLazy = m[f].$association.$lazy;
|
|
408
|
+
if (seen.indexOf(target) >= 0) {
|
|
409
|
+
cycles.push({field: prefix + p + f, assoc: m[f].$association});
|
|
410
|
+
if (isToMany) {
|
|
411
|
+
// force projection, but without recursion
|
|
412
|
+
utils.setPropPath(projection, prefix + p + f, {$all: true});
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
assocs.push({
|
|
416
|
+
field: prefix + p + f, target: targetEntity, toMany: isToMany, lazy: isLazy,
|
|
417
|
+
});
|
|
418
|
+
if (!isLazy) {
|
|
419
|
+
_build(targetEntity, `${prefix + p + f }.`, seen.concat([target]));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
_build(entity, '', [entityName]);
|
|
394
426
|
|
|
395
|
-
|
|
396
|
-
|
|
427
|
+
logger.debug(`node-cds import: analyzed ${ entityName }: proj = ${
|
|
428
|
+
JSON.stringify(projection) }, ${ assocs.length } assocs${ cycles.length } cycles`);
|
|
397
429
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
430
|
+
return {
|
|
431
|
+
projection: projection,
|
|
432
|
+
associations: assocs,
|
|
433
|
+
cycles: cycles,
|
|
434
|
+
};
|
|
435
|
+
};
|
|
404
436
|
|
|
405
437
|
|
|
406
438
|
// (async) retrieve SQL metadata for database table
|
|
407
439
|
function getSqlMetadata(tableName, schemaName, callback) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
440
|
+
const sqlname = schemaName ? `${schemaName }.${ tableName}` : tableName;
|
|
441
|
+
if (sqlname in sqlMetadata) {
|
|
442
|
+
return callback(null, sqlMetadata[sqlname]);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// get type information
|
|
446
|
+
const metadata = {};
|
|
447
|
+
const processResult = function(err, rows) {
|
|
448
|
+
if (err) {
|
|
449
|
+
return callback(err);
|
|
411
450
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
processResult(err, result);
|
|
446
|
-
});
|
|
447
|
-
});
|
|
451
|
+
if (rows.length == 0) {
|
|
452
|
+
return callback(`database table ${ tableName } not found`);
|
|
453
|
+
}
|
|
454
|
+
for (let i = 0; i < rows.length; i++) {
|
|
455
|
+
const columnName = rows[i].COLUMN_NAME;
|
|
456
|
+
metadata[columnName] = {
|
|
457
|
+
$type: rows[i].DATA_TYPE_ID,
|
|
458
|
+
$csType: rows[i].CS_DATA_TYPE_ID,
|
|
459
|
+
$size: rows[i].SCALE,
|
|
460
|
+
$key: rows[i].IS_PRIMARY_KEY === 'TRUE',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
sqlMetadata[sqlname] = metadata;
|
|
464
|
+
return callback(null, metadata);
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
transaction.getClient(null, function(err, client) {
|
|
468
|
+
if (err) {
|
|
469
|
+
return callback(err);
|
|
470
|
+
}
|
|
471
|
+
const schema = schemaName ? `'${ schemaName }'` : 'CURRENT_SCHEMA';
|
|
472
|
+
client.exec(
|
|
473
|
+
// eslint-disable-next-line max-len
|
|
474
|
+
`${'SELECT tc.COLUMN_NAME, tc.DATA_TYPE_ID, tc.CS_DATA_TYPE_ID, tc.SCALE, cs.IS_PRIMARY_KEY ' +
|
|
475
|
+
'FROM SYS.TABLE_COLUMNS tc LEFT OUTER JOIN SYS.CONSTRAINTS cs ' +
|
|
476
|
+
'ON tc.SCHEMA_NAME = cs.SCHEMA_NAME AND tc.TABLE_NAME = cs.TABLE_NAME ' +
|
|
477
|
+
'AND tc.COLUMN_NAME = cs.COLUMN_NAME ' +
|
|
478
|
+
'WHERE tc.SCHEMA_NAME = '}${ schema } AND tc.TABLE_NAME = '${ tableName }'`,
|
|
479
|
+
function(err, result) {
|
|
480
|
+
transaction.releaseClient(client);
|
|
481
|
+
processResult(err, result);
|
|
482
|
+
});
|
|
483
|
+
});
|
|
448
484
|
};
|
|
449
485
|
|
|
450
486
|
// (async) retrieve CDS metadata from unofficial CDS metadata tables
|
|
451
487
|
function getCdsMetadata(fullname, schema, options, callback) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
488
|
+
if (options.$noCds) {
|
|
489
|
+
return callback(null, null);
|
|
490
|
+
}
|
|
491
|
+
if (fullname in cdsMetadata) {
|
|
492
|
+
return callback(null, cdsMetadata[fullname]);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// prepare queries for retrieving assocs and structs
|
|
496
|
+
const sqlA =
|
|
459
497
|
'SELECT e.ARTIFACT_NAME AS "n",' +
|
|
460
498
|
' a.SCHEMA_NAME, e.SCHEMA_NAME,' +
|
|
461
499
|
' a.TARGET_ARTIFACT_SCHEMA_NAME AS "ts",' +
|
|
462
500
|
' a.TARGET_ARTIFACT_NAME AS "tn",' +
|
|
463
|
-
' a.JOIN_CONDITION AS "on",' +
|
|
464
|
-
' e.ELEMENT_NAME AS "cn",' +
|
|
465
|
-
' e.AUX_ELEMENT_INFO AS "fk"' +
|
|
501
|
+
' a.JOIN_CONDITION AS "on",' + // unmanaged assoc
|
|
502
|
+
' e.ELEMENT_NAME AS "cn",' + // component name
|
|
503
|
+
' e.AUX_ELEMENT_INFO AS "fk"' + // alias name
|
|
466
504
|
' FROM SYS.CDS_ARTIFACT_DEFINITION(?, ?) AS e JOIN SYS.CDS_ASSOCIATIONS AS a' +
|
|
467
505
|
' ON e.ARTIFACT_NAME = a.ASSOCIATION_NAME AND a.SCHEMA_NAME = e.SCHEMA_NAME' +
|
|
468
506
|
' WHERE e.ARTIFACT_KIND = \'ASSOCIATION_ELEMENT\' OR' +
|
|
469
507
|
' (e.ARTIFACT_KIND = \'ASSOCIATION\' AND a.ASSOCIATION_KIND = \'UNMANAGED\')';
|
|
470
|
-
|
|
508
|
+
const sqlACS =
|
|
471
509
|
'SELECT e.ARTIFACT_NAME AS "n",' +
|
|
472
510
|
' a.SCHEMA_NAME, e.SCHEMA_NAME,' +
|
|
473
511
|
' a.TARGET_ARTIFACT_SCHEMA_NAME AS "ts",' +
|
|
474
512
|
' a.TARGET_ARTIFACT_NAME AS "tn",' +
|
|
475
|
-
' a.JOIN_CONDITION AS "on",' +
|
|
476
|
-
' e.ELEMENT_NAME AS "cn",' +
|
|
477
|
-
' e.AUX_ELEMENT_INFO AS "fk"' +
|
|
513
|
+
' a.JOIN_CONDITION AS "on",' + // unmanaged assoc
|
|
514
|
+
' e.ELEMENT_NAME AS "cn",' + // component name
|
|
515
|
+
' e.AUX_ELEMENT_INFO AS "fk"' + // alias name
|
|
516
|
+
// eslint-disable-next-line max-len
|
|
478
517
|
' FROM SYS.CDS_ARTIFACT_DEFINITION(CURRENT_SCHEMA, ?) AS e JOIN SYS.CDS_ASSOCIATIONS AS a' +
|
|
479
518
|
' ON e.ARTIFACT_NAME = a.ASSOCIATION_NAME AND a.SCHEMA_NAME = e.SCHEMA_NAME' +
|
|
480
519
|
' WHERE e.ARTIFACT_KIND = \'ASSOCIATION_ELEMENT\' OR' +
|
|
481
520
|
' (e.ARTIFACT_KIND = \'ASSOCIATION\' AND a.ASSOCIATION_KIND = \'UNMANAGED\')';
|
|
482
|
-
|
|
521
|
+
const sqlS =
|
|
483
522
|
'SELECT ELEMENT_NAME AS "n",' +
|
|
484
523
|
' USED_ARTIFACT_SCHEMA AS "ts",' +
|
|
485
524
|
' USED_ARTIFACT_NAME AS "tn"' +
|
|
486
525
|
' FROM SYS.CDS_ARTIFACT_DEFINITION(?, ?)' +
|
|
487
526
|
' WHERE USED_ARTIFACT_KIND = \'STRUCTURED_TYPE\'';
|
|
488
|
-
|
|
527
|
+
const sqlSCS =
|
|
489
528
|
'SELECT ELEMENT_NAME AS "n",' +
|
|
490
529
|
' USED_ARTIFACT_SCHEMA AS "ts",' +
|
|
491
530
|
' USED_ARTIFACT_NAME AS "tn"' +
|
|
492
531
|
' FROM SYS.CDS_ARTIFACT_DEFINITION(CURRENT_SCHEMA, ?)' +
|
|
493
532
|
' WHERE USED_ARTIFACT_KIND = \'STRUCTURED_TYPE\'';
|
|
494
533
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
534
|
+
transaction.getClient(null, function(err, client) {
|
|
535
|
+
if (err) {
|
|
536
|
+
return callback(err);
|
|
537
|
+
}
|
|
538
|
+
async.series([
|
|
539
|
+
function(cb) {
|
|
540
|
+
client.prepare(sqlA, cb);
|
|
541
|
+
},
|
|
542
|
+
function(cb) {
|
|
543
|
+
client.prepare(sqlACS, cb);
|
|
544
|
+
},
|
|
545
|
+
function(cb) {
|
|
546
|
+
client.prepare(sqlS, cb);
|
|
547
|
+
},
|
|
548
|
+
function(cb) {
|
|
549
|
+
client.prepare(sqlSCS, cb);
|
|
550
|
+
},
|
|
551
|
+
], function(err, stmts) {
|
|
552
|
+
if (err) {
|
|
553
|
+
throw new Error('*** ASSERT FAIL *** invalid CDS metadata query');
|
|
554
|
+
}
|
|
555
|
+
getStructsAndAssocs(stmts, schema, fullname, function(err, data) {
|
|
556
|
+
transaction.releaseClient(client);
|
|
557
|
+
cdsMetadata[fullname] = data;
|
|
558
|
+
return callback(err, data);
|
|
559
|
+
});
|
|
512
560
|
});
|
|
561
|
+
});
|
|
513
562
|
}
|
|
514
563
|
|
|
515
564
|
// async
|
|
516
565
|
// recursively retrieve assoc and struct information
|
|
517
566
|
function getStructsAndAssocs(stmts, schema, fullname, callback) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
return callback(null, assocs);
|
|
550
|
-
});
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
// retrieve structure metadata from CDS tables
|
|
554
|
-
var getStructs = function(callback) {
|
|
555
|
-
var s = schema ? stmts[2] : stmts[3];
|
|
556
|
-
var a = schema ? [schema, fullname] : [fullname];
|
|
557
|
-
s.exec(a, function(err, rows) {
|
|
558
|
-
var structs = {};
|
|
559
|
-
if (err)
|
|
560
|
-
return callback("Error retrieving CDS type metadata: " + err, structs);
|
|
561
|
-
for (var i = 0; i < rows.length; ++i) {
|
|
562
|
-
var componentName = rows[i].n;
|
|
563
|
-
structs[componentName] = {
|
|
564
|
-
schema: rows[i].ts,
|
|
565
|
-
name: rows[i].tn
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
return callback(null, structs);
|
|
569
|
-
});
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
// recursively get assocs and structs
|
|
573
|
-
getAssocs(function (err, assocs) {
|
|
574
|
-
if (err)
|
|
575
|
-
return callback(err, null);
|
|
576
|
-
getStructs(function (err, structs) {
|
|
577
|
-
if (err)
|
|
578
|
-
return callback(err, null);
|
|
579
|
-
var fns = [];
|
|
580
|
-
for (var s in structs) {
|
|
581
|
-
var fn = (function (result, field) {
|
|
582
|
-
return function (cb) {
|
|
583
|
-
getStructsAndAssocs(stmts, structs[field].schema, structs[field].name,
|
|
584
|
-
function (err, data) {
|
|
585
|
-
if (err)
|
|
586
|
-
return cb(err, null);
|
|
587
|
-
result[field] = data;
|
|
588
|
-
cb(null); // result is ignored
|
|
589
|
-
});
|
|
590
|
-
};
|
|
591
|
-
})(assocs, s);
|
|
592
|
-
fns.push(fn);
|
|
567
|
+
logger.debug(`node-cds import: retrieve CDS assoc for ${ fullname}`);
|
|
568
|
+
|
|
569
|
+
// retrieve association metadata from CDS tables
|
|
570
|
+
const getAssocs = function(callback) {
|
|
571
|
+
const s = schema ? stmts[0] : stmts[1];
|
|
572
|
+
const a = schema ? [schema, fullname] : [fullname];
|
|
573
|
+
s.exec(a, function(err, rows) {
|
|
574
|
+
const assocs = {};
|
|
575
|
+
if (err) {
|
|
576
|
+
return callback(`Error retrieving CDS association metadata: ${ err}`, assocs);
|
|
577
|
+
}
|
|
578
|
+
for (let i = 0; i < rows.length; ++i) {
|
|
579
|
+
const fieldName = rows[i].n.slice(fullname.length + 1);
|
|
580
|
+
if (!(fieldName in assocs)) {
|
|
581
|
+
assocs[fieldName] = {
|
|
582
|
+
$association: rows[i].tn,
|
|
583
|
+
$schema: rows[i].ts,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (rows[i].on && rows[i].on.length) {
|
|
587
|
+
const cond = String.fromCharCode.apply(null, rows[i].on);
|
|
588
|
+
assocs[fieldName].$on = cond;
|
|
589
|
+
} else {
|
|
590
|
+
const foreignKey = rows[i].cn;
|
|
591
|
+
const foreignKeyAlias = rows[i].fk;
|
|
592
|
+
if (foreignKey !== foreignKeyAlias && foreignKeyAlias !== null) {
|
|
593
|
+
if (!('$aliases' in assocs[fieldName])) {
|
|
594
|
+
assocs[fieldName].$aliases = {};
|
|
593
595
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
596
|
+
assocs[fieldName].$aliases[foreignKey] = foreignKeyAlias;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return callback(null, assocs);
|
|
601
|
+
});
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// retrieve structure metadata from CDS tables
|
|
605
|
+
const getStructs = function(callback) {
|
|
606
|
+
const s = schema ? stmts[2] : stmts[3];
|
|
607
|
+
const a = schema ? [schema, fullname] : [fullname];
|
|
608
|
+
s.exec(a, function(err, rows) {
|
|
609
|
+
const structs = {};
|
|
610
|
+
if (err) {
|
|
611
|
+
return callback(`Error retrieving CDS type metadata: ${ err}`, structs);
|
|
612
|
+
}
|
|
613
|
+
for (let i = 0; i < rows.length; ++i) {
|
|
614
|
+
const componentName = rows[i].n;
|
|
615
|
+
structs[componentName] = {
|
|
616
|
+
schema: rows[i].ts,
|
|
617
|
+
name: rows[i].tn,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
return callback(null, structs);
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// recursively get assocs and structs
|
|
625
|
+
getAssocs(function(err, assocs) {
|
|
626
|
+
if (err) {
|
|
627
|
+
return callback(err, null);
|
|
628
|
+
}
|
|
629
|
+
getStructs(function(err, structs) {
|
|
630
|
+
if (err) {
|
|
631
|
+
return callback(err, null);
|
|
632
|
+
}
|
|
633
|
+
const fns = [];
|
|
634
|
+
for (const s in structs) {
|
|
635
|
+
const fn = (function(result, field) {
|
|
636
|
+
return function(cb) {
|
|
637
|
+
getStructsAndAssocs(stmts, structs[field].schema, structs[field].name,
|
|
638
|
+
function(err, data) {
|
|
639
|
+
if (err) {
|
|
640
|
+
return cb(err, null);
|
|
641
|
+
}
|
|
642
|
+
result[field] = data;
|
|
643
|
+
cb(null); // result is ignored
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
})(assocs, s);
|
|
647
|
+
fns.push(fn);
|
|
648
|
+
}
|
|
649
|
+
async.parallel(fns, function(err, ignored) {
|
|
650
|
+
callback(err, assocs);
|
|
651
|
+
});
|
|
598
652
|
});
|
|
653
|
+
});
|
|
599
654
|
}
|
|
600
655
|
|
|
601
656
|
function buildSqlMapping(sqlMetadata, ignoredColumns) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
657
|
+
ignoredColumns = ignoredColumns || [];
|
|
658
|
+
// add all SQL columns as fields
|
|
659
|
+
const mapping = {};
|
|
660
|
+
for (const columnName in sqlMetadata) {
|
|
661
|
+
if (ignoredColumns.indexOf(columnName) >= 0) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const field = utils.mkPropPath(mapping, columnName);
|
|
665
|
+
field.$column = columnName;
|
|
666
|
+
if ('$key' in sqlMetadata[columnName]) {
|
|
667
|
+
field.$key = sqlMetadata[columnName].$key;
|
|
612
668
|
}
|
|
613
|
-
|
|
669
|
+
}
|
|
670
|
+
return mapping;
|
|
614
671
|
}
|
|
615
672
|
|
|
616
673
|
function buildCdsMapping(cdsMetadata) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
674
|
+
// convert CDS association metadata into mapping format
|
|
675
|
+
const assocs = {};
|
|
676
|
+
utils.forStruct(cdsMetadata, {
|
|
677
|
+
$association: function(s, f, p) {
|
|
678
|
+
const a = utils.mkPropPath(assocs, p + f);
|
|
679
|
+
a.$association = {
|
|
680
|
+
$entity: s[f].$association,
|
|
681
|
+
$schema: s[f].$schema,
|
|
682
|
+
$lazy: false,
|
|
683
|
+
};
|
|
684
|
+
if (s[f].$on) {
|
|
685
|
+
a.$association.$on = s[f].$on;
|
|
686
|
+
}
|
|
687
|
+
if (s[f].$aliases) {
|
|
688
|
+
a.$aliases = s[f].$aliases;
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
return assocs;
|
|
634
693
|
}
|
|
635
694
|
|
|
636
695
|
function mergeMapping(mapping, newFields) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
} else if (src[f] === false) {
|
|
655
|
-
// remove prop in dst
|
|
656
|
-
delete dst[f];
|
|
657
|
-
} else {
|
|
658
|
-
// copy prop from src to dst
|
|
659
|
-
dst[f] = src[f];
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
//var result = Entities.cloneJSON(mapping);
|
|
665
|
-
copy(mapping, newFields);
|
|
666
|
-
|
|
667
|
-
// aliases: rename fields by moving
|
|
668
|
-
utils.forStruct(mapping, {
|
|
669
|
-
$aliases: function (s, f, p) {
|
|
670
|
-
var aliases = s[f].$aliases;
|
|
671
|
-
for (var oldf in aliases) {
|
|
672
|
-
var newf = aliases[oldf];
|
|
673
|
-
utils.setPropPath(s[f], newf, s[f][oldf]);
|
|
674
|
-
delete s[f][oldf];
|
|
675
|
-
}
|
|
676
|
-
delete s[f].$aliases;
|
|
696
|
+
// recursively copy nested properties from src to dst
|
|
697
|
+
const copy = function(dst, src) {
|
|
698
|
+
// special handling for $association/$column overrides
|
|
699
|
+
if ('$column' in src && '$association' in dst ||
|
|
700
|
+
'$association' in src && '$column' in dst) {
|
|
701
|
+
for (const p in dst) {
|
|
702
|
+
delete dst[p];
|
|
703
|
+
}
|
|
704
|
+
copy(dst, src); // redo
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
// copy properties
|
|
708
|
+
for (const f in src) {
|
|
709
|
+
if (typeof src[f] === 'object') {
|
|
710
|
+
// merge props of src structure
|
|
711
|
+
if (!(f in dst)) {
|
|
712
|
+
dst[f] = {};
|
|
677
713
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
714
|
+
copy(dst[f], src[f]);
|
|
715
|
+
} else if (src[f] === false) {
|
|
716
|
+
// remove prop in dst
|
|
717
|
+
delete dst[f];
|
|
718
|
+
} else {
|
|
719
|
+
// copy prop from src to dst
|
|
720
|
+
dst[f] = src[f];
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// var result = Entities.cloneJSON(mapping);
|
|
726
|
+
copy(mapping, newFields);
|
|
727
|
+
|
|
728
|
+
// aliases: rename fields by moving
|
|
729
|
+
utils.forStruct(mapping, {
|
|
730
|
+
$aliases: function(s, f, p) {
|
|
731
|
+
const aliases = s[f].$aliases;
|
|
732
|
+
for (const oldf in aliases) {
|
|
733
|
+
const newf = aliases[oldf];
|
|
734
|
+
utils.setPropPath(s[f], newf, s[f][oldf]);
|
|
735
|
+
delete s[f][oldf];
|
|
736
|
+
}
|
|
737
|
+
delete s[f].$aliases;
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
return mapping;
|
|
681
742
|
}
|
|
682
743
|
|
|
683
744
|
// various validity and consistency checks for mapping
|
|
684
745
|
function checkMapping(mapping) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
|
|
746
|
+
let error = null;
|
|
747
|
+
utils.forStruct(mapping, {
|
|
748
|
+
$association: function(s, f, p) {
|
|
749
|
+
const assoc = s[f].$association;
|
|
750
|
+
if ('$on' in assoc && '$cascadeDiscard' in assoc) {
|
|
751
|
+
error = 'Cascade discard invalid for unmanaged associations';
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
});
|
|
755
|
+
return error;
|
|
694
756
|
}
|
|
695
757
|
|
|
696
758
|
|
|
697
759
|
// testing and debugging
|
|
698
760
|
|
|
699
761
|
exports._clearImports = function() {
|
|
700
|
-
|
|
701
|
-
|
|
762
|
+
logger.info('node-cds: reset imports');
|
|
763
|
+
knownEntities = {};
|
|
702
764
|
};
|