@isrd-isi-edu/ermrestjs 2.0.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/LICENSE +202 -0
- package/README.md +55 -0
- package/dist/ermrest.d.ts +3481 -0
- package/dist/ermrest.js +45 -0
- package/dist/ermrest.js.gz +0 -0
- package/dist/ermrest.js.map +1 -0
- package/dist/ermrest.min.js +45 -0
- package/dist/ermrest.min.js.gz +0 -0
- package/dist/ermrest.min.js.map +1 -0
- package/dist/ermrest.ver.txt +1 -0
- package/dist/stats.html +4949 -0
- package/js/ag_reference.js +1483 -0
- package/js/core.js +4931 -0
- package/js/datapath.js +336 -0
- package/js/export.js +956 -0
- package/js/filters.js +192 -0
- package/js/format.js +344 -0
- package/js/hatrac.js +1130 -0
- package/js/json_ld_validator.js +285 -0
- package/js/parser.js +2320 -0
- package/js/setup/node.js +27 -0
- package/js/utils/helpers.js +2300 -0
- package/js/utils/json_ld_schema.js +680 -0
- package/js/utils/pseudocolumn_helpers.js +2196 -0
- package/package.json +79 -0
- package/src/index.ts +204 -0
- package/src/models/comment.ts +14 -0
- package/src/models/deferred-promise.ts +16 -0
- package/src/models/display-name.ts +5 -0
- package/src/models/errors.ts +408 -0
- package/src/models/path-prefix-alias-mapping.ts +130 -0
- package/src/models/reference/bulk-create-foreign-key-object.ts +133 -0
- package/src/models/reference/citation.ts +98 -0
- package/src/models/reference/contextualize.ts +535 -0
- package/src/models/reference/google-dataset-metadata.ts +72 -0
- package/src/models/reference/index.ts +14 -0
- package/src/models/reference/page.ts +520 -0
- package/src/models/reference/reference-aggregate-fn.ts +37 -0
- package/src/models/reference/reference.ts +2813 -0
- package/src/models/reference/related-reference.ts +467 -0
- package/src/models/reference/tuple.ts +652 -0
- package/src/models/reference-column/asset-pseudo-column.ts +498 -0
- package/src/models/reference-column/column-aggregate.ts +313 -0
- package/src/models/reference-column/facet-column.ts +1380 -0
- package/src/models/reference-column/foreign-key-pseudo-column.ts +626 -0
- package/src/models/reference-column/inbound-foreign-key-pseudo-column.ts +131 -0
- package/src/models/reference-column/index.ts +13 -0
- package/src/models/reference-column/key-pseudo-column.ts +236 -0
- package/src/models/reference-column/pseudo-column.ts +850 -0
- package/src/models/reference-column/reference-column.ts +740 -0
- package/src/models/source-object-node.ts +156 -0
- package/src/models/source-object-wrapper.ts +694 -0
- package/src/models/table-source-definitions.ts +98 -0
- package/src/services/authn.ts +43 -0
- package/src/services/catalog.ts +37 -0
- package/src/services/config.ts +202 -0
- package/src/services/error.ts +247 -0
- package/src/services/handlebars.ts +607 -0
- package/src/services/history.ts +136 -0
- package/src/services/http.ts +536 -0
- package/src/services/logger.ts +70 -0
- package/src/services/mustache.ts +0 -0
- package/src/utils/column-utils.ts +308 -0
- package/src/utils/constants.ts +526 -0
- package/src/utils/markdown-utils.ts +855 -0
- package/src/utils/reference-utils.ts +1658 -0
- package/src/utils/template-utils.ts +0 -0
- package/src/utils/type-utils.ts +89 -0
- package/src/utils/value-utils.ts +127 -0
- package/tsconfig.json +30 -0
- package/vite.config.mts +104 -0
package/js/export.js
ADDED
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-this-alias */
|
|
2
|
+
|
|
3
|
+
// models
|
|
4
|
+
import { InvalidInputError } from '@isrd-isi-edu/ermrestjs/src/models/errors';
|
|
5
|
+
// import DeferredPromise from '@isrd-isi-edu/ermrestjs/src/models/deferred-promise';
|
|
6
|
+
|
|
7
|
+
// services
|
|
8
|
+
import $log from '@isrd-isi-edu/ermrestjs/src/services/logger';
|
|
9
|
+
import ErrorService from '@isrd-isi-edu/ermrestjs/src/services/error';
|
|
10
|
+
import ConfigService from '@isrd-isi-edu/ermrestjs/src/services/config';
|
|
11
|
+
|
|
12
|
+
// utils
|
|
13
|
+
import { isObjectAndNotNull, isStringAndNotEmpty, ObjectHasAllKeys } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
14
|
+
import { fixedEncodeURIComponent, trimSlashes, simpleDeepCopy } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
|
|
15
|
+
import { _annotations, contextHeaderName, _contexts, _exportKnownAPIs, URL_PATH_LENGTH_LIMIT } from '@isrd-isi-edu/ermrestjs/src/utils/constants';
|
|
16
|
+
|
|
17
|
+
// legacy
|
|
18
|
+
import {
|
|
19
|
+
_getAnnotationValueByContext,
|
|
20
|
+
_getRecursiveAnnotationValue,
|
|
21
|
+
_getCandidateRowNameColumn,
|
|
22
|
+
_sanitizeFilename,
|
|
23
|
+
} from '@isrd-isi-edu/ermrestjs/js/utils/helpers';
|
|
24
|
+
|
|
25
|
+
export const _exportHelpers = {
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* Returns the export templates that are defined on this table.
|
|
29
|
+
* NOTE If this returns `null`, then the exportTemplates is not defined on the table or schema
|
|
30
|
+
* NOTE The returned array should not be directly used as it might be using fragments
|
|
31
|
+
* @param {Table} table
|
|
32
|
+
* @param {string} context
|
|
33
|
+
* @private
|
|
34
|
+
* @ignore
|
|
35
|
+
*/
|
|
36
|
+
getExportAnnotTemplates: function (table, context) {
|
|
37
|
+
var exp = _annotations.EXPORT,
|
|
38
|
+
expCtx = _annotations.EXPORT_CONTEXTED,
|
|
39
|
+
annotDefinition = {},
|
|
40
|
+
hasAnnot = false,
|
|
41
|
+
chosenAnnot,
|
|
42
|
+
templates = [];
|
|
43
|
+
|
|
44
|
+
// start from table, then try schema, and then catalog
|
|
45
|
+
[table, table.schema, table.schema.catalog].forEach(function (el) {
|
|
46
|
+
if (hasAnnot) return;
|
|
47
|
+
|
|
48
|
+
// get from table annotation
|
|
49
|
+
if (el.annotations.contains(exp)) {
|
|
50
|
+
annotDefinition = { '*': el.annotations.get(exp).content };
|
|
51
|
+
hasAnnot = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// get from table contextualized annotation
|
|
55
|
+
if (el.annotations.contains(expCtx)) {
|
|
56
|
+
annotDefinition = Object.assign({}, annotDefinition, el.annotations.get(expCtx).content);
|
|
57
|
+
hasAnnot = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (hasAnnot) {
|
|
61
|
+
// find the annotation defined for the context
|
|
62
|
+
chosenAnnot = _getAnnotationValueByContext(context, annotDefinition);
|
|
63
|
+
|
|
64
|
+
// not defined for the context
|
|
65
|
+
if (chosenAnnot === -1) {
|
|
66
|
+
hasAnnot = false;
|
|
67
|
+
}
|
|
68
|
+
// make sure it's the correct format
|
|
69
|
+
else if (isObjectAndNotNull(chosenAnnot) && 'templates' in chosenAnnot && Array.isArray(chosenAnnot.templates)) {
|
|
70
|
+
templates = chosenAnnot.templates;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (hasAnnot) {
|
|
76
|
+
return templates;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Return the export fragments that should be used with export annotation.
|
|
84
|
+
* @param {Table} table
|
|
85
|
+
* @param {Object} defaultExportTemplate
|
|
86
|
+
* @returns An object that can be used in combination with export annotation
|
|
87
|
+
* @private
|
|
88
|
+
* @ignore
|
|
89
|
+
*/
|
|
90
|
+
getExportFragmentObject: function (table, defaultExportTemplate) {
|
|
91
|
+
var exportFragments = {
|
|
92
|
+
$chaise_default_bdbag_template: {
|
|
93
|
+
type: 'BAG',
|
|
94
|
+
displayname: { fragment_key: '$chaise_default_bdbag_displayname' },
|
|
95
|
+
outputs: [{ fragment_key: '$chaise_default_bdbag_outputs' }],
|
|
96
|
+
},
|
|
97
|
+
$chaise_default_bdbag_displayname: 'BDBag',
|
|
98
|
+
$chaise_default_bdbag_outputs: defaultExportTemplate ? defaultExportTemplate.outputs : null,
|
|
99
|
+
};
|
|
100
|
+
var annotKey = _annotations.EXPORT_FRAGMENT_DEFINITIONS,
|
|
101
|
+
annot;
|
|
102
|
+
[table.schema.catalog, table.schema, table].forEach(function (el) {
|
|
103
|
+
if (!el.annotations.contains(annotKey)) return;
|
|
104
|
+
|
|
105
|
+
annot = el.annotations.get(annotKey).content;
|
|
106
|
+
if (isObjectAndNotNull(annot)) {
|
|
107
|
+
// remove the keys that start with $
|
|
108
|
+
Object.keys(annot)
|
|
109
|
+
.filter(function (k) {
|
|
110
|
+
return k.startsWith('$');
|
|
111
|
+
})
|
|
112
|
+
.forEach(function (k) {
|
|
113
|
+
$log.warn('Export: ignoring `' + k + '` fragment as it cannot start with $.');
|
|
114
|
+
delete annot[k];
|
|
115
|
+
});
|
|
116
|
+
Object.assign(exportFragments, annot);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return exportFragments;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Replace the fragments used in templates with actual definition
|
|
125
|
+
* @param {Array} templates the template definitions
|
|
126
|
+
* @param {*} exportFragments the fragment object
|
|
127
|
+
* @returns An array of templates that should be validated and used
|
|
128
|
+
* @private
|
|
129
|
+
* @ignore
|
|
130
|
+
*/
|
|
131
|
+
replaceFragments: function (templates, exportFragments) {
|
|
132
|
+
var hasError;
|
|
133
|
+
|
|
134
|
+
// traverse through the object and replace fragment with the actual definition
|
|
135
|
+
var _replaceFragments = function (obj, usedFragments) {
|
|
136
|
+
if (hasError) return null;
|
|
137
|
+
|
|
138
|
+
if (!usedFragments) {
|
|
139
|
+
usedFragments = {};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var res, intRes;
|
|
143
|
+
|
|
144
|
+
// if it's an array, then we have to process each individual value
|
|
145
|
+
if (Array.isArray(obj)) {
|
|
146
|
+
res = [];
|
|
147
|
+
obj.forEach(function (item) {
|
|
148
|
+
// flatten the values and just concat with each other
|
|
149
|
+
intRes = _replaceFragments(item, usedFragments);
|
|
150
|
+
if (intRes == null || hasError) {
|
|
151
|
+
res = null;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
res = res.concat(intRes);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return res;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// if it's an object, we have to see whether it's fragment or not
|
|
161
|
+
if (isObjectAndNotNull(obj)) {
|
|
162
|
+
if ('fragment_key' in obj) {
|
|
163
|
+
var fragmentKey = obj.fragment_key;
|
|
164
|
+
|
|
165
|
+
// there was a cycle, so just set the variables and abort
|
|
166
|
+
if (fragmentKey in usedFragments) {
|
|
167
|
+
$log.warn(`Export: circular dependency detected in the defined templates and therefore ignored (caused by ${fragmentKey} key)`);
|
|
168
|
+
hasError = true;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// fragment_key is invalid
|
|
173
|
+
if (!(fragmentKey in exportFragments)) {
|
|
174
|
+
hasError = true;
|
|
175
|
+
$log.warn('Export: the given fragment_key `' + fragmentKey + '` is not valid and template will be ignored.');
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// replace with actual definition
|
|
180
|
+
var modified = simpleDeepCopy(usedFragments);
|
|
181
|
+
modified[fragmentKey] = true;
|
|
182
|
+
return _replaceFragments(exportFragments[fragmentKey], modified);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// run the function for each value
|
|
186
|
+
res = {};
|
|
187
|
+
for (var k in obj) {
|
|
188
|
+
intRes = _replaceFragments(obj[k], usedFragments);
|
|
189
|
+
if (intRes == null || hasError) {
|
|
190
|
+
res = null;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
res[k] = intRes;
|
|
194
|
+
}
|
|
195
|
+
return res;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// for other data types, just return the input without any change
|
|
199
|
+
return obj;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
var finalRes = [];
|
|
203
|
+
|
|
204
|
+
templates.forEach(function (t) {
|
|
205
|
+
hasError = false;
|
|
206
|
+
var tempRes = _replaceFragments(t, {});
|
|
207
|
+
// if there was an issue, the whole thing should return null
|
|
208
|
+
if (tempRes != null) {
|
|
209
|
+
finalRes = finalRes.concat(tempRes);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return finalRes;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Given an object, returns a boolean that indicates whether it is a valid template or not
|
|
219
|
+
* NOTE This will only validate the structure and not the values
|
|
220
|
+
* @param {Object} template
|
|
221
|
+
* @return {boolean}
|
|
222
|
+
*/
|
|
223
|
+
export const validateExportTemplate = function (template) {
|
|
224
|
+
var errMessage = function (reason) {
|
|
225
|
+
$log.info('export template ignored with displayname=`' + template.displayname + '`. Reason: ' + reason);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// template is not an object
|
|
229
|
+
if (template !== Object(template) || Array.isArray(template) || !template) {
|
|
230
|
+
$log.info("export template ignored. Reason: it's not an object.");
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// doesn't have the expected attributes
|
|
235
|
+
if (!ObjectHasAllKeys(template, ['displayname', 'type'])) {
|
|
236
|
+
$log.info('export template ignored. Reason: first level required attributes are missing.');
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
//type must be either FILE or BAG
|
|
241
|
+
if (['BAG', 'FILE'].indexOf(template.type) === -1) {
|
|
242
|
+
$log.info('export template ignored. Reason: template.type must be either `BAG` or `FILE`.');
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// in FILE, outputs must be properly defined
|
|
247
|
+
if (!Array.isArray(template.outputs) || template.outputs.length === 0) {
|
|
248
|
+
errMessage('outputs must be a non-empty array.');
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
var output;
|
|
253
|
+
for (var i = 0; i < template.outputs.length; i++) {
|
|
254
|
+
output = template.outputs[i];
|
|
255
|
+
|
|
256
|
+
//output must be an object
|
|
257
|
+
if (output !== Object(output) || Array.isArray(output) || !output) {
|
|
258
|
+
errMessage('output index=' + i + ' is not an object.');
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// output must have source and destination
|
|
263
|
+
if (!ObjectHasAllKeys(output, ['source', 'destination'])) {
|
|
264
|
+
errMessage('output index=' + i + ' has missing required attributes.');
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// output.source must have api
|
|
269
|
+
if (!ObjectHasAllKeys(output.source, ['api'])) {
|
|
270
|
+
errMessage('output.source index=' + i + ' has missing required attributes.');
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// output.destination must have at least a type
|
|
275
|
+
if (!ObjectHasAllKeys(output.destination, ['type'])) {
|
|
276
|
+
errMessage('output.destination index=' + i + ' has missing required attributes.');
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return true;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @desc Export Object
|
|
286
|
+
*
|
|
287
|
+
* @memberof ERMrest
|
|
288
|
+
* @class
|
|
289
|
+
* @param {Reference} reference
|
|
290
|
+
* @param {String} bagName the name that will be used for the bag
|
|
291
|
+
* @param {Object} template the tempalte must be in the valid format.
|
|
292
|
+
* @param {String} servicePath the path to the service, i.e. "/deriva/export/"
|
|
293
|
+
*
|
|
294
|
+
*
|
|
295
|
+
* @returns {Export}
|
|
296
|
+
* @constructor
|
|
297
|
+
*/
|
|
298
|
+
export function Exporter(reference, bagName, template, servicePath) {
|
|
299
|
+
if (!validateExportTemplate(template)) {
|
|
300
|
+
throw new InvalidInputError('Given Template is not valid.');
|
|
301
|
+
}
|
|
302
|
+
if (typeof servicePath !== 'string' || servicePath.length === 0) {
|
|
303
|
+
throw new InvalidInputError('Given service path is not valid.');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
this.reference = reference;
|
|
307
|
+
this.template = template;
|
|
308
|
+
this.servicePath = trimSlashes(servicePath);
|
|
309
|
+
this.formatOptions = {
|
|
310
|
+
BAG: {
|
|
311
|
+
name: bagName,
|
|
312
|
+
algs: ['md5'],
|
|
313
|
+
archiver: 'zip',
|
|
314
|
+
metadata: {},
|
|
315
|
+
table_format: 'csv',
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
Exporter.prototype = {
|
|
321
|
+
constructor: Exporter,
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* TODO: add description
|
|
325
|
+
*/
|
|
326
|
+
get exportParameters() {
|
|
327
|
+
if (this._exportParameters === undefined) {
|
|
328
|
+
var exportParameters = {};
|
|
329
|
+
var bagOptions = this.formatOptions.BAG;
|
|
330
|
+
var template = this.template;
|
|
331
|
+
|
|
332
|
+
var bagParameters = {
|
|
333
|
+
bag_name: bagOptions.name,
|
|
334
|
+
bag_algorithms: bagOptions.algs,
|
|
335
|
+
bag_archiver: bagOptions.archiver,
|
|
336
|
+
bag_metadata: bagOptions.metadata,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
exportParameters.bag = bagParameters;
|
|
340
|
+
|
|
341
|
+
var base_url = this.reference.location.service;
|
|
342
|
+
var queries = [];
|
|
343
|
+
var catalogParameters = {
|
|
344
|
+
host: base_url.substring(0, base_url.lastIndexOf('/')),
|
|
345
|
+
catalog_id: this.reference.location.catalog,
|
|
346
|
+
query_processors: queries,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
exportParameters.catalog = catalogParameters;
|
|
350
|
+
|
|
351
|
+
if (!template) {
|
|
352
|
+
// this is basically the same as a single file CSV or JSON export but packaged as a bag
|
|
353
|
+
var query = {
|
|
354
|
+
processor: bagOptions.table_format,
|
|
355
|
+
processor_params: {
|
|
356
|
+
output_path: bagOptions.name,
|
|
357
|
+
query_path: '/' + this.reference.location.api + '/' + this.reference.location.ermrestCompactPath + '?limit=none',
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
queries.push(query);
|
|
362
|
+
} else {
|
|
363
|
+
var outputs = template.outputs;
|
|
364
|
+
var predicate = this.reference.location.ermrestCompactPath;
|
|
365
|
+
var table = this.reference.table.name;
|
|
366
|
+
var output_path = _sanitizeFilename(this.reference.displayname.unformatted);
|
|
367
|
+
|
|
368
|
+
outputs.forEach(function (output, index) {
|
|
369
|
+
var source = output.source,
|
|
370
|
+
dest = output.destination;
|
|
371
|
+
var query = {},
|
|
372
|
+
queryParams = {};
|
|
373
|
+
|
|
374
|
+
// <api>/<current reference path>/<path>
|
|
375
|
+
var queryFrags = [];
|
|
376
|
+
if (isStringAndNotEmpty(source.api)) {
|
|
377
|
+
queryFrags.push(source.api);
|
|
378
|
+
}
|
|
379
|
+
if (!source.skip_root_path) {
|
|
380
|
+
queryFrags.push(predicate);
|
|
381
|
+
}
|
|
382
|
+
if (isStringAndNotEmpty(source.path)) {
|
|
383
|
+
// remove the first and last slash if it has one
|
|
384
|
+
const addedPath = trimSlashes(source.path);
|
|
385
|
+
// make sure the path is not empty
|
|
386
|
+
if (addedPath.length > 0) {
|
|
387
|
+
queryFrags.push(addedPath);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
var queryStr = queryFrags.join('/');
|
|
392
|
+
if (queryStr.length > URL_PATH_LENGTH_LIMIT) {
|
|
393
|
+
$log.warn(
|
|
394
|
+
'Cannot send the output index `' + index + '` for table `' + table + '` to ermrest (URL LENGTH ERROR). Generated query:',
|
|
395
|
+
queryStr,
|
|
396
|
+
);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// find the character that should be used for added q params
|
|
401
|
+
var qParamCharacter = queryStr.indexOf('?') !== -1 ? '&' : '?';
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* add limit q param if all the following are set
|
|
405
|
+
* - skip_limit is not set to true
|
|
406
|
+
* - API is known.
|
|
407
|
+
* - it's not part of the url
|
|
408
|
+
*/
|
|
409
|
+
var addLimit =
|
|
410
|
+
!source.skip_limit &&
|
|
411
|
+
isStringAndNotEmpty(queryStr) &&
|
|
412
|
+
_exportKnownAPIs.some(function (api) {
|
|
413
|
+
return queryStr.startsWith(api + '/');
|
|
414
|
+
});
|
|
415
|
+
// if limit is already part of the query, don't add it.
|
|
416
|
+
if (addLimit) {
|
|
417
|
+
addLimit = !/[?]([^&=]*=[^&]*[&])*limit=/.test(queryStr);
|
|
418
|
+
}
|
|
419
|
+
if (addLimit) {
|
|
420
|
+
queryStr += qParamCharacter + 'limit=none';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
queryParams.query_path = '/' + queryStr;
|
|
424
|
+
queryParams.output_path = dest.name || output_path;
|
|
425
|
+
if (dest.impl != null) {
|
|
426
|
+
query.processor_type = dest.impl;
|
|
427
|
+
}
|
|
428
|
+
if (dest.params != null) {
|
|
429
|
+
Object.assign(queryParams, dest.params);
|
|
430
|
+
}
|
|
431
|
+
query.processor = dest.type || bagOptions.table_format;
|
|
432
|
+
query.processor_params = queryParams;
|
|
433
|
+
queries.push(query);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (template.transforms != null) {
|
|
437
|
+
exportParameters.transform_processors = template.transforms;
|
|
438
|
+
}
|
|
439
|
+
if (template.postprocessors != null) {
|
|
440
|
+
exportParameters.post_processors = template.postprocessors;
|
|
441
|
+
}
|
|
442
|
+
if (template.public != null) {
|
|
443
|
+
exportParameters.public = template.public;
|
|
444
|
+
}
|
|
445
|
+
if (template.bag_archiver != null) {
|
|
446
|
+
exportParameters.bag.bag_archiver = template.bag_archiver;
|
|
447
|
+
}
|
|
448
|
+
if (template.bag_idempotent != null) {
|
|
449
|
+
exportParameters.bag.bag_idempotent = template.bag_idempotent;
|
|
450
|
+
}
|
|
451
|
+
this._exportParameters = exportParameters;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return this._exportParameters;
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* sends the export request to ioboxd
|
|
459
|
+
* @param {Object} contextHeaderParams the object that will be logged
|
|
460
|
+
* @returns {Promise}
|
|
461
|
+
*/
|
|
462
|
+
run: function (contextHeaderParams) {
|
|
463
|
+
var defer = ConfigService.q.defer(),
|
|
464
|
+
self = this;
|
|
465
|
+
try {
|
|
466
|
+
var serviceUrl = [self.exportParameters.catalog.host, self.servicePath, self.template.type == 'BAG' ? 'bdbag' : 'file'].join('/');
|
|
467
|
+
|
|
468
|
+
// log parameters
|
|
469
|
+
var headers = {};
|
|
470
|
+
if (!contextHeaderParams || typeof contextHeaderParams !== 'object') {
|
|
471
|
+
contextHeaderParams = { action: 'export' };
|
|
472
|
+
}
|
|
473
|
+
// add the reference information
|
|
474
|
+
for (var key in self.reference.defaultLogInfo) {
|
|
475
|
+
if (key in contextHeaderParams) continue;
|
|
476
|
+
contextHeaderParams[key] = self.reference.defaultLogInfo[key];
|
|
477
|
+
}
|
|
478
|
+
headers[contextHeaderName] = contextHeaderParams;
|
|
479
|
+
|
|
480
|
+
self.canceled = false;
|
|
481
|
+
if (self.exportParameters.public != null) {
|
|
482
|
+
serviceUrl += '?public=' + self.exportParameters.public;
|
|
483
|
+
}
|
|
484
|
+
self.reference._server.http
|
|
485
|
+
.post(serviceUrl, self.exportParameters, { headers: headers })
|
|
486
|
+
.then(function success(response) {
|
|
487
|
+
defer.resolve({ data: response.data.split('\n'), canceled: self.canceled });
|
|
488
|
+
})
|
|
489
|
+
.catch(function (err) {
|
|
490
|
+
var error = ErrorService.responseToError(err);
|
|
491
|
+
defer.reject(error);
|
|
492
|
+
});
|
|
493
|
+
} catch (e) {
|
|
494
|
+
defer.reject(e);
|
|
495
|
+
}
|
|
496
|
+
return defer.promise;
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Will set the canceled flag so when the datat comes back, we can tell the client
|
|
501
|
+
* to ignore the value. If it is already canceled it won't do anything.
|
|
502
|
+
* @return {boolean} returns false if the export is already canceled
|
|
503
|
+
*/
|
|
504
|
+
cancel: function () {
|
|
505
|
+
if (this.canceled) return false;
|
|
506
|
+
this.canceled = true;
|
|
507
|
+
return true;
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Try export/<context> then export then 'detailed'
|
|
513
|
+
* @param {reference} ref
|
|
514
|
+
* @param {Boolean} useCompact - whether the current context is compact or not
|
|
515
|
+
*/
|
|
516
|
+
export const _getExportReference = function (ref, useCompact) {
|
|
517
|
+
var detCtx = _contexts.DETAILED,
|
|
518
|
+
expCompCtx = _contexts.EXPORT_COMPACT,
|
|
519
|
+
expDetCtx = _contexts.EXPORT_DETAILED;
|
|
520
|
+
|
|
521
|
+
var isContext = function (context) {
|
|
522
|
+
return context == ref._context;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
var hasColumns = function (ctx) {
|
|
526
|
+
var res = _getRecursiveAnnotationValue(ctx, ref.table.annotations.get(_annotations.VISIBLE_COLUMNS).content, true);
|
|
527
|
+
return res !== -1 && Array.isArray(res);
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
var useMainContext = function () {
|
|
531
|
+
return isContext(detCtx) ? ref : ref.contextualize.detailed;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
if (ref.table.annotations.contains(_annotations.VISIBLE_COLUMNS)) {
|
|
535
|
+
// export/<context>
|
|
536
|
+
// NOTE even if only export context is defined, the visible-columns logic will handle it
|
|
537
|
+
if (useCompact) {
|
|
538
|
+
if (hasColumns(expCompCtx)) {
|
|
539
|
+
return isContext(expCompCtx) ? ref : ref.contextualize.exportCompact;
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
if (hasColumns(expDetCtx)) {
|
|
543
|
+
return isContext(expDetCtx) ? ref : ref.contextualize.exportDetailed;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// <context> or no annot
|
|
549
|
+
return useMainContext();
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Given a reference object, will return the appropriate output object.
|
|
554
|
+
* It might use attributegroup or entity apis based on the situation.
|
|
555
|
+
*
|
|
556
|
+
* given a reference and the path from main table to it (it might be empty),
|
|
557
|
+
* will generate the appropriate output.
|
|
558
|
+
* - If addMainKey is true,
|
|
559
|
+
* it will add the shortestkey of main table to the projection list.
|
|
560
|
+
* - will go based on visible columns defined for `export` (if not `detailed`):
|
|
561
|
+
* - Normal Columns: add to the projection list.
|
|
562
|
+
* - ForeignKey pseudo-column: Add the constituent columns alongside extra "candidate" columns (if needed).
|
|
563
|
+
* - Key pseudo-column: add the constituent columns.
|
|
564
|
+
* - Asset pseudo-column: add all the metadata columns alongside the url value.
|
|
565
|
+
* - Inline table: not applicable. Because of the one-to-many nature of the relationship this is not feasible.
|
|
566
|
+
* - other pseudo-columns (aggregate): not applicable.
|
|
567
|
+
* If the genarated attributegroup path is long, will fall back to the entity api
|
|
568
|
+
*
|
|
569
|
+
* In case of attributegroup, we will change the projection list.
|
|
570
|
+
* Assume that this is the model:
|
|
571
|
+
* main_table <- t1 <- t2
|
|
572
|
+
* the key list will be based on:
|
|
573
|
+
* - shortestkey of main_table and t2 (with alias name in `<tablename>.<shortestkey_column_name>` format)
|
|
574
|
+
* the projection list will be based on:
|
|
575
|
+
* - visible columns of t2
|
|
576
|
+
* - "candidate" columns of the foreignkeys (with alias name in `<tablename>.<candidate_column_name>` format)
|
|
577
|
+
*
|
|
578
|
+
* by "candidate" we mean columns that might make more sense to user instead of the typical "RID" or "ID".
|
|
579
|
+
* These are the same column names that we are using for row-name generation.
|
|
580
|
+
*
|
|
581
|
+
* @private
|
|
582
|
+
* @param {reference} ref the reference that we want the output for
|
|
583
|
+
* @param {String} tableAlias the alias that is used for projecting table (last table in path)
|
|
584
|
+
* @param {String=} path the string that will be prepended to the path
|
|
585
|
+
* @param {boolean=} addMainKey whether we want to add the key of the main table.
|
|
586
|
+
* if this is true, the next parameter is required.
|
|
587
|
+
* @param {Reference=} mainRef The main reference
|
|
588
|
+
* @return {any} the output object
|
|
589
|
+
*/
|
|
590
|
+
export const _referenceExportOutput = function (ref, tableAlias, path, addMainKey, mainRef, useCompact) {
|
|
591
|
+
var projectionList = [],
|
|
592
|
+
keyList = [],
|
|
593
|
+
name,
|
|
594
|
+
i = 0,
|
|
595
|
+
consideredFks = {},
|
|
596
|
+
addedCols = {},
|
|
597
|
+
usedNames = {},
|
|
598
|
+
shortestKeyCols = {},
|
|
599
|
+
fkeys = [],
|
|
600
|
+
fkAlias,
|
|
601
|
+
candidate,
|
|
602
|
+
addedColPrefix;
|
|
603
|
+
|
|
604
|
+
var encode = fixedEncodeURIComponent;
|
|
605
|
+
|
|
606
|
+
// find the candidate column of the table
|
|
607
|
+
var getCandidateColumn = function (table) {
|
|
608
|
+
return _getCandidateRowNameColumn(
|
|
609
|
+
table.columns.all().map(function (col) {
|
|
610
|
+
return col.name;
|
|
611
|
+
}),
|
|
612
|
+
);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// check whether the given column is a candidate column
|
|
616
|
+
var isCandidateColumn = function (column) {
|
|
617
|
+
return _getCandidateRowNameColumn([column.name]) !== false;
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
var addColumn = function (c) {
|
|
621
|
+
var columns = Array.isArray(c) ? c : [c];
|
|
622
|
+
columns.forEach(function (col) {
|
|
623
|
+
if (col == null || typeof col !== 'object' || addedCols[col.name]) return;
|
|
624
|
+
addedCols[col.name] = true;
|
|
625
|
+
projectionList.push(encode(col.name));
|
|
626
|
+
});
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// don't use any of the table column names
|
|
630
|
+
ref.table.columns.all().forEach(function (col) {
|
|
631
|
+
usedNames[col.name] = true;
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// shortestkey of the current reference
|
|
635
|
+
ref.table.shortestKey.forEach(function (col) {
|
|
636
|
+
keyList.push(encode(col.name));
|
|
637
|
+
addedCols[col.name] = true;
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// if it's a related entity and we need to key of the main table
|
|
641
|
+
if (addMainKey) {
|
|
642
|
+
// we have to add the shortestkey of main table
|
|
643
|
+
addedColPrefix = encode(mainRef.table.name) + '.';
|
|
644
|
+
mainRef.table.shortestKey.forEach(function (col) {
|
|
645
|
+
shortestKeyCols[col.name] = true;
|
|
646
|
+
name = addedColPrefix + encode(col.name);
|
|
647
|
+
// make sure the alias doesn't exist in the table
|
|
648
|
+
while (name in usedNames) {
|
|
649
|
+
name = addedColPrefix + encode(col.name) + '_' + ++i;
|
|
650
|
+
}
|
|
651
|
+
usedNames[name] = true;
|
|
652
|
+
keyList.push(name + ':=' + mainRef.location.mainTableAlias + ':' + encode(col.name));
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
//add the candidate column of main table too
|
|
656
|
+
candidate = getCandidateColumn(mainRef.table);
|
|
657
|
+
if (candidate && !(candidate in shortestKeyCols)) {
|
|
658
|
+
name = addedColPrefix + encode(candidate);
|
|
659
|
+
// make sure the alias doesn't exist in the table
|
|
660
|
+
while (name in usedNames) {
|
|
661
|
+
name = addedColPrefix + encode(candidate) + '_' + ++i;
|
|
662
|
+
}
|
|
663
|
+
usedNames[name] = true;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
var exportRef = _getExportReference(ref, useCompact);
|
|
668
|
+
|
|
669
|
+
if (exportRef.columns.length === 0) {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
exportRef.columns.forEach(function (col) {
|
|
674
|
+
if (!col.isPseudo) {
|
|
675
|
+
addColumn(col);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (col.isForeignKey || (col.isPathColumn && col.isUnique && col.isEntityMode)) {
|
|
680
|
+
if (consideredFks[col.name]) return;
|
|
681
|
+
consideredFks[col.name] = true;
|
|
682
|
+
|
|
683
|
+
// add the constituent columns
|
|
684
|
+
var hasCandidate = false;
|
|
685
|
+
var firstFk = col.firstForeignKeyNode.nodeObject;
|
|
686
|
+
firstFk.colset.columns.forEach(function (fkeyCol) {
|
|
687
|
+
addColumn(fkeyCol);
|
|
688
|
+
if (!hasCandidate && col.foreignKeyPathLength === 1 && isCandidateColumn(firstFk.mapping.get(fkeyCol))) {
|
|
689
|
+
hasCandidate = true;
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// if any of the constituent columns is candidate, don't add fk projection
|
|
694
|
+
if (hasCandidate) return;
|
|
695
|
+
|
|
696
|
+
// find the candidate column in the referred table;
|
|
697
|
+
candidate = getCandidateColumn(col.lastForeignKeyNode.nodeObject.key.table);
|
|
698
|
+
|
|
699
|
+
// we couldn't find any candidate columns
|
|
700
|
+
if (!candidate) return;
|
|
701
|
+
|
|
702
|
+
// add the fkey
|
|
703
|
+
fkAlias = 'F' + (fkeys.length + 1);
|
|
704
|
+
|
|
705
|
+
var fkeyPath = [];
|
|
706
|
+
col.sourceObjectNodes.forEach(function (f) {
|
|
707
|
+
if (f.isFilter) {
|
|
708
|
+
fkeyPath.push(f.toString());
|
|
709
|
+
} else {
|
|
710
|
+
fkeyPath.push((f === col.lastForeignKeyNode ? fkAlias + ':=' : '') + f.toString(false, true));
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
// path to the foreignkey + reset the path to the main table
|
|
715
|
+
fkeys.push(fkeyPath.join('/') + '/$' + tableAlias);
|
|
716
|
+
|
|
717
|
+
// add to projectionList
|
|
718
|
+
addedColPrefix = encode(col.table.name) + '.';
|
|
719
|
+
name = addedColPrefix + encode(candidate);
|
|
720
|
+
i = 0;
|
|
721
|
+
while (name in usedNames) {
|
|
722
|
+
name = addedColPrefix + encode(candidate) + '_' + ++i;
|
|
723
|
+
}
|
|
724
|
+
usedNames[name] = true;
|
|
725
|
+
name = name + ':=' + fkAlias + ':' + candidate;
|
|
726
|
+
|
|
727
|
+
projectionList.push(name);
|
|
728
|
+
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (col.isKey) {
|
|
733
|
+
// add constituent columns
|
|
734
|
+
col.key.colset.columns.forEach(addColumn);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (col.isAsset) {
|
|
739
|
+
// add the column alongside the metadata columns
|
|
740
|
+
addColumn([col, col.filenameColumn, col.byteCountColumn, col.md5, col.sha256]);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// other pseudo-columns won't be added
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
// generate the path, based on given values.
|
|
748
|
+
var exportPath = typeof path === 'string' ? path + '/' : '';
|
|
749
|
+
if (fkeys.length > 0) {
|
|
750
|
+
exportPath += fkeys.join('/') + '/';
|
|
751
|
+
}
|
|
752
|
+
exportPath += keyList.join(',') + ';' + projectionList.join(',');
|
|
753
|
+
|
|
754
|
+
if (exportPath.length > URL_PATH_LENGTH_LIMIT) {
|
|
755
|
+
$log.warn('Cannot use attributegroup api for exporting `' + ref.table.name + '` because of url limitation.');
|
|
756
|
+
return _referenceExportEntityOutput(ref, path);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return {
|
|
760
|
+
destination: {
|
|
761
|
+
name: _sanitizeFilename(ref.displayname.unformatted),
|
|
762
|
+
type: 'csv',
|
|
763
|
+
},
|
|
764
|
+
source: {
|
|
765
|
+
api: 'attributegroup',
|
|
766
|
+
path: exportPath,
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Given a reference object, will return the appropriate output object using entity api
|
|
773
|
+
* @private
|
|
774
|
+
* @param {Reference} ref the reference object
|
|
775
|
+
* @param {String} path the string that will be prepended to the path
|
|
776
|
+
* @return {Object} the output object
|
|
777
|
+
*/
|
|
778
|
+
export const _referenceExportEntityOutput = function (ref, path) {
|
|
779
|
+
var source = {
|
|
780
|
+
api: 'entity',
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
if (path) {
|
|
784
|
+
source.path = path;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
return {
|
|
788
|
+
destination: {
|
|
789
|
+
name: _sanitizeFilename(ref.displayname.unformatted),
|
|
790
|
+
type: 'csv',
|
|
791
|
+
},
|
|
792
|
+
source: source,
|
|
793
|
+
};
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Given a column will return the appropriate output object for asset.
|
|
798
|
+
* It will return null if column is not an asset.
|
|
799
|
+
* @private
|
|
800
|
+
* @param {Column} col the column object
|
|
801
|
+
* @param {String} destinationPath the string that will be prepended to destination path
|
|
802
|
+
* @param {String} sourcePath the string that will be prepended to source path
|
|
803
|
+
* @return {Object}
|
|
804
|
+
*/
|
|
805
|
+
export const _getAssetExportOutput = function (col, destinationPath, sourcePath) {
|
|
806
|
+
if (!col.isAsset) return null;
|
|
807
|
+
|
|
808
|
+
var path = [],
|
|
809
|
+
key;
|
|
810
|
+
var sanitize = _sanitizeFilename,
|
|
811
|
+
encode = fixedEncodeURIComponent;
|
|
812
|
+
|
|
813
|
+
// attributes
|
|
814
|
+
var attributes = {
|
|
815
|
+
byteCountColumn: 'length',
|
|
816
|
+
filenameColumn: 'filename',
|
|
817
|
+
md5: 'md5',
|
|
818
|
+
sha256: 'sha256',
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
// add the url
|
|
822
|
+
path.push('url:=' + encode(col.name));
|
|
823
|
+
|
|
824
|
+
// add the attributes (ignore the ones that are not defined)
|
|
825
|
+
for (key in attributes) {
|
|
826
|
+
if (col[key] == null) continue;
|
|
827
|
+
path.push(attributes[key] + ':=' + encode(col[key].name));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return {
|
|
831
|
+
destination: {
|
|
832
|
+
name: 'assets/' + (destinationPath ? sanitize(destinationPath) + '/' : '') + sanitize(col.name),
|
|
833
|
+
type: 'fetch',
|
|
834
|
+
},
|
|
835
|
+
source: {
|
|
836
|
+
api: 'attribute',
|
|
837
|
+
// exporter will throw an error if the url is null, so we are adding the check for not-null.
|
|
838
|
+
path: (sourcePath ? sourcePath + '/' : '') + '!(' + encode(col.name) + '::null::)/' + path.join(','),
|
|
839
|
+
},
|
|
840
|
+
};
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Returns a object, that can be used as a default export template.
|
|
845
|
+
* NOTE SHOULD ONLY BE USED IN DETAILED CONTEXT
|
|
846
|
+
* It will include:
|
|
847
|
+
* - csv of the main table.
|
|
848
|
+
* - csv of all the related entities
|
|
849
|
+
* - fetch all the assets. For fetch, we need to provide url, length, and md5 (or other checksum types).
|
|
850
|
+
* if these columns are missing from the asset annotation, they won't be added.
|
|
851
|
+
* - fetch all the assetes of related tables.
|
|
852
|
+
* @param {Reference} reference the reference object
|
|
853
|
+
*/
|
|
854
|
+
export const _getDefaultExportTemplate = function (reference) {
|
|
855
|
+
const outputs = [],
|
|
856
|
+
relatedTableAlias = 'R';
|
|
857
|
+
|
|
858
|
+
const getTableOutput = _referenceExportOutput,
|
|
859
|
+
getAssetOutput = _getAssetExportOutput;
|
|
860
|
+
|
|
861
|
+
const addOutput = function (output) {
|
|
862
|
+
if (output != null) {
|
|
863
|
+
outputs.push(output);
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// create a csv + fetch all the assets
|
|
868
|
+
const processRelatedReference = function (rel) {
|
|
869
|
+
// the path that will be used for assets of related entities
|
|
870
|
+
const destinationPath = rel.displayname.unformatted;
|
|
871
|
+
// this will be used for source path
|
|
872
|
+
let sourcePath;
|
|
873
|
+
if (rel.pseudoColumn && !rel.pseudoColumn.isInboundForeignKey) {
|
|
874
|
+
// const lastFk = rel.pseudoColumn.sourceObjectWrapper.lastForeignKeyNode;
|
|
875
|
+
// path from main to the related reference
|
|
876
|
+
sourcePath = rel.pseudoColumn.sourceObjectWrapper.toString(false, false, relatedTableAlias);
|
|
877
|
+
|
|
878
|
+
// path more than length one, we need to add the main table fkey
|
|
879
|
+
addOutput(getTableOutput(rel, relatedTableAlias, sourcePath, rel.pseudoColumn.foreignKeyPathLength >= 2, reference));
|
|
880
|
+
}
|
|
881
|
+
// association table
|
|
882
|
+
else if (rel.derivedAssociationReference) {
|
|
883
|
+
const assoc = rel.derivedAssociationReference;
|
|
884
|
+
sourcePath = assoc.origFKR.toString() + '/' + relatedTableAlias + ':=' + assoc.associationToRelatedFKR.toString(true);
|
|
885
|
+
addOutput(getTableOutput(rel, relatedTableAlias, sourcePath, true, reference));
|
|
886
|
+
}
|
|
887
|
+
// single inbound related
|
|
888
|
+
else {
|
|
889
|
+
sourcePath = relatedTableAlias + ':=' + rel.origFKR.toString(false, false);
|
|
890
|
+
addOutput(getTableOutput(rel, relatedTableAlias, sourcePath));
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// add asset of the related table
|
|
894
|
+
const expRef = _getExportReference(rel);
|
|
895
|
+
|
|
896
|
+
// alternative table, don't add asset
|
|
897
|
+
if (expRef.table !== rel.table) return;
|
|
898
|
+
|
|
899
|
+
expRef.columns.forEach(function (col) {
|
|
900
|
+
const output = getAssetOutput(col, destinationPath, sourcePath);
|
|
901
|
+
addOutput(output);
|
|
902
|
+
});
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// main entity
|
|
906
|
+
addOutput(getTableOutput(reference, reference.location.mainTableAlias));
|
|
907
|
+
|
|
908
|
+
const exportRef = _getExportReference(reference);
|
|
909
|
+
|
|
910
|
+
// we're not supporting alternative tables
|
|
911
|
+
if (exportRef.table.name === reference.table.name) {
|
|
912
|
+
// main assets
|
|
913
|
+
exportRef.columns.forEach(function (col) {
|
|
914
|
+
const output = getAssetOutput(col, '', '');
|
|
915
|
+
addOutput(output);
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
// inline entities
|
|
919
|
+
exportRef.columns.forEach(function (col) {
|
|
920
|
+
if (col.isInboundForeignKey || (col.isPathColumn && col.hasPath && !col.isUnique && !col.hasAggregate)) {
|
|
921
|
+
return processRelatedReference(col.reference);
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// related entities (use the export context otherwise detailed)
|
|
927
|
+
let hasRelatedExport = false;
|
|
928
|
+
if (reference.table.annotations.contains(_annotations.VISIBLE_FOREIGN_KEYS)) {
|
|
929
|
+
const exportRelated = _getRecursiveAnnotationValue(
|
|
930
|
+
_contexts.EXPORT,
|
|
931
|
+
reference.table.annotations.get(_annotations.VISIBLE_FOREIGN_KEYS).content,
|
|
932
|
+
true,
|
|
933
|
+
);
|
|
934
|
+
hasRelatedExport = exportRelated !== -1 && Array.isArray(exportRelated);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// if export context is defined in visible-foreign-keys, use it, otherwise fallback to detailed
|
|
938
|
+
const exportRefForRelated = hasRelatedExport
|
|
939
|
+
? reference.contextualize.export
|
|
940
|
+
: reference._context === _contexts.DETAILED
|
|
941
|
+
? reference
|
|
942
|
+
: reference.contextualize.detailed;
|
|
943
|
+
if (exportRefForRelated.table.name === reference.table.name) {
|
|
944
|
+
exportRefForRelated.related.forEach(processRelatedReference);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (outputs.length === 0) {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return {
|
|
952
|
+
displayname: 'BDBag',
|
|
953
|
+
type: 'BAG',
|
|
954
|
+
outputs: outputs,
|
|
955
|
+
};
|
|
956
|
+
};
|