@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
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
// models
|
|
2
|
+
import SourceObjectWrapper from '@isrd-isi-edu/ermrestjs/src/models/source-object-wrapper';
|
|
3
|
+
import SourceObjectNode from '@isrd-isi-edu/ermrestjs/src/models/source-object-node';
|
|
4
|
+
import { ReferenceColumn, ReferenceColumnTypes } from '@isrd-isi-edu/ermrestjs/src/models/reference-column';
|
|
5
|
+
import { CommentType } from '@isrd-isi-edu/ermrestjs/src/models/comment';
|
|
6
|
+
import { DisplayName } from '@isrd-isi-edu/ermrestjs/src/models/display-name';
|
|
7
|
+
import { Reference, RelatedReference, type Page, type Tuple } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
8
|
+
|
|
9
|
+
// services
|
|
10
|
+
import $log from '@isrd-isi-edu/ermrestjs/src/services/logger';
|
|
11
|
+
import ErrorService from '@isrd-isi-edu/ermrestjs/src/services/error';
|
|
12
|
+
|
|
13
|
+
// utils
|
|
14
|
+
import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils';
|
|
15
|
+
import { isDefinedAndNotNull, isObject, isObjectAndNotNull, isStringAndNotEmpty, verify } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
16
|
+
import { fixedEncodeURIComponent } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
|
|
17
|
+
import { processAggregateValue } from '@isrd-isi-edu/ermrestjs/src/utils/column-utils';
|
|
18
|
+
import {
|
|
19
|
+
_pseudoColAggregateFns,
|
|
20
|
+
_pseudoColEntityAggregateFns,
|
|
21
|
+
_pseudoColAggregateExplicitName,
|
|
22
|
+
_pseudoColAggregateNames,
|
|
23
|
+
URL_PATH_LENGTH_LIMIT,
|
|
24
|
+
} from '@isrd-isi-edu/ermrestjs/src/utils/constants';
|
|
25
|
+
|
|
26
|
+
// legacy
|
|
27
|
+
import { Column, Key } from '@isrd-isi-edu/ermrestjs/js/core';
|
|
28
|
+
import { parse } from '@isrd-isi-edu/ermrestjs/js/parser';
|
|
29
|
+
import {
|
|
30
|
+
_isEntryContext,
|
|
31
|
+
_getFormattedKeyValues,
|
|
32
|
+
_getRowTemplateVariables,
|
|
33
|
+
_generateRowPresentation,
|
|
34
|
+
generateKeyValueFilters,
|
|
35
|
+
processMarkdownPattern,
|
|
36
|
+
_processModelComment,
|
|
37
|
+
_processSourceObjectComment,
|
|
38
|
+
} from '@isrd-isi-edu/ermrestjs/js/utils/helpers';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A pseudo-column without any actual source definition behind it.
|
|
42
|
+
* This constructor assumes that the sourceObject has markdown_name and display.markdown_pattern.
|
|
43
|
+
*
|
|
44
|
+
* The name is currently generated by the visible columns logic. It will use the
|
|
45
|
+
* "$<markdown_name>" pattern and if a column with this name already exists in the table,
|
|
46
|
+
* it will append "-<integer>" to it.
|
|
47
|
+
*/
|
|
48
|
+
export class VirtualColumn extends ReferenceColumn {
|
|
49
|
+
public isPseudo: boolean = true;
|
|
50
|
+
|
|
51
|
+
isVirtualColumn = true;
|
|
52
|
+
|
|
53
|
+
constructor(reference: Reference, sourceObjectWrapper: SourceObjectWrapper, name: string, mainTuple?: Tuple) {
|
|
54
|
+
super(reference, [], sourceObjectWrapper, name, mainTuple);
|
|
55
|
+
|
|
56
|
+
this.referenceColumnType = ReferenceColumnTypes.VIRTUAL;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* If you want to create an object of this type, use the `createPseudoColumn` method.
|
|
62
|
+
* This will only be used for general purpose pseudo-columns, using that method ensures That
|
|
63
|
+
* we're creating the more specific object instead. Therefore only these cases should
|
|
64
|
+
* be using this type of object:
|
|
65
|
+
* 1. When sourceObject has aggregate
|
|
66
|
+
* 2. When sourceObject has a path that is not just an outbound fk, or it doesn't define a related
|
|
67
|
+
* entity (inbound or p&b association)
|
|
68
|
+
*
|
|
69
|
+
* @memberof ERMrest
|
|
70
|
+
* @param {Reference} reference column's reference
|
|
71
|
+
* @param {Column} column the column that this pseudo-column is representing
|
|
72
|
+
* @param {SourceObjectWrapper} sourceObjectWrapper the sourceObjectWrapper object (might be undefined)
|
|
73
|
+
* @param {string=} name to avoid processing the name again, this might be undefined.
|
|
74
|
+
* @param {Tuple=} mainTuple if the reference is referring to just one tuple, this is defined.
|
|
75
|
+
* @constructor
|
|
76
|
+
* @class
|
|
77
|
+
*/
|
|
78
|
+
export class PseudoColumn extends ReferenceColumn {
|
|
79
|
+
/**
|
|
80
|
+
* indicates that this object represents a PseudoColumn.
|
|
81
|
+
*/
|
|
82
|
+
public isPseudo: boolean = true;
|
|
83
|
+
|
|
84
|
+
public isPathColumn: boolean = true;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* If the pseudo-column is connected via a path to the table or not.
|
|
88
|
+
*/
|
|
89
|
+
public hasPath: boolean;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* If the pseudoColumn is in entity mode
|
|
93
|
+
*/
|
|
94
|
+
public isEntityMode: boolean;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* If the pseudoColumn is referring to a unique row (the path is one to one)
|
|
98
|
+
*/
|
|
99
|
+
public isUnique: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* If aggregate function is defined on the column.
|
|
103
|
+
*/
|
|
104
|
+
public hasAggregate: boolean;
|
|
105
|
+
|
|
106
|
+
public baseColumn: Column;
|
|
107
|
+
|
|
108
|
+
constructor(reference: Reference, column: Column, sourceObjectWrapper: SourceObjectWrapper, name?: string, mainTuple?: Tuple) {
|
|
109
|
+
super(reference, [column], sourceObjectWrapper, name, mainTuple);
|
|
110
|
+
|
|
111
|
+
this.referenceColumnType = ReferenceColumnTypes.PSEUDO;
|
|
112
|
+
|
|
113
|
+
this.hasPath = this.sourceObjectWrapper!.hasPath;
|
|
114
|
+
this.isEntityMode = this.sourceObjectWrapper!.isEntityMode;
|
|
115
|
+
this.isUnique = this.sourceObjectWrapper!.isUnique;
|
|
116
|
+
this.hasAggregate = this.sourceObjectWrapper!.hasAggregate;
|
|
117
|
+
|
|
118
|
+
this.baseColumn = column;
|
|
119
|
+
this._currentTable = reference.table;
|
|
120
|
+
this.table = column.table;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Format the presentation value corresponding to this pseudo-column definition.
|
|
125
|
+
* 1. If source is not in entity mode: use the column's heuristic
|
|
126
|
+
* 2. Otherwise if it's not a path, apply the same logic as KeyPseudoColumn presentation based on the key.
|
|
127
|
+
* 2. Otherwise if path is one to one (all outbound), use the same logic as ForeignKeyPseudoColumn based on last fk.
|
|
128
|
+
* 3. Otherwise return null value.
|
|
129
|
+
*
|
|
130
|
+
* @param {Object} data the raw data of the table
|
|
131
|
+
* @param {String=} context the app context (optional)
|
|
132
|
+
* @param {Object=} templateVariables the template variables that should be used (optional)
|
|
133
|
+
* @param {Object=} options (optional)
|
|
134
|
+
* @returns {Object} A key value pair containing value and isHTML that detemrines the presentation.
|
|
135
|
+
*/
|
|
136
|
+
formatPresentation(data: any = {}, context?: string, templateVariables?: any, options: any = {}): any {
|
|
137
|
+
if (!isStringAndNotEmpty(context)) {
|
|
138
|
+
context = this._context;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const nullValue = {
|
|
142
|
+
isHTML: false,
|
|
143
|
+
value: this._getNullValue(context!),
|
|
144
|
+
unformatted: this._getNullValue(context!),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (_isEntryContext(context)) {
|
|
148
|
+
return nullValue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (this.hasWaitFor && !options.skipWaitFor) {
|
|
152
|
+
return nullValue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// has aggregate, we should get the value by calling aggregate function
|
|
156
|
+
if (this.hasAggregate) {
|
|
157
|
+
return nullValue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// not representing a row
|
|
161
|
+
if (!this.isUnique) {
|
|
162
|
+
return nullValue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// make sure templateVariables is valid
|
|
166
|
+
if (!isObjectAndNotNull(templateVariables)) {
|
|
167
|
+
templateVariables = _getFormattedKeyValues(this.table, context!, data);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// not in entity mode, just return the column value.
|
|
171
|
+
if (!this.isEntityMode) {
|
|
172
|
+
// we should not pass the same templateVariables to the parent,
|
|
173
|
+
// since when it goes to the parent it will be based on the leaf table
|
|
174
|
+
// while the templateVariables is based on the parent table.
|
|
175
|
+
// only if we're going to use this with sourceMarkdownPattern we should pass this value
|
|
176
|
+
return super.formatPresentation(data, context, this.display.sourceMarkdownPattern ? templateVariables : null);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.display.sourceMarkdownPattern) {
|
|
180
|
+
const keyValues: any = {};
|
|
181
|
+
const selfTemplateVariables = {
|
|
182
|
+
$self: _getRowTemplateVariables(this.table, context!, data),
|
|
183
|
+
};
|
|
184
|
+
Object.assign(keyValues, templateVariables, selfTemplateVariables);
|
|
185
|
+
return processMarkdownPattern(this.display.sourceMarkdownPattern, keyValues, this.table, context!, {
|
|
186
|
+
templateEngine: this.display.sourceTemplateEngine,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// in entity mode, return the foreignkey value
|
|
191
|
+
const pres = _generateRowPresentation(this.lastForeignKeyNode!.nodeObject.key, data, context!, this._getShowForeignKeyLink(context!));
|
|
192
|
+
return pres ? pres : nullValue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
sourceFormatPresentation(templateVariables: any, columnValue: any, mainTuple: Tuple) {
|
|
196
|
+
const baseCol = this.baseColumn;
|
|
197
|
+
const context = this._context;
|
|
198
|
+
let selfTemplateVariables: any = {};
|
|
199
|
+
|
|
200
|
+
if (this.display.sourceMarkdownPattern) {
|
|
201
|
+
// for aggregate, the columnValue has the value precomputed
|
|
202
|
+
if (this.hasAggregate && columnValue) {
|
|
203
|
+
selfTemplateVariables = columnValue.templateVariables;
|
|
204
|
+
}
|
|
205
|
+
// all-outbound paths
|
|
206
|
+
else if (this.hasPath && this.isUnique) {
|
|
207
|
+
// use the linked data if exists
|
|
208
|
+
if (!mainTuple.linkedData[this.name]) {
|
|
209
|
+
selfTemplateVariables = {};
|
|
210
|
+
}
|
|
211
|
+
// scalar default
|
|
212
|
+
else if (!this.isEntityMode) {
|
|
213
|
+
selfTemplateVariables = {
|
|
214
|
+
$self: baseCol.formatvalue(mainTuple.linkedData[this.name][baseCol.name], context),
|
|
215
|
+
$_self: mainTuple.linkedData[this.name][baseCol.name],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// entity default
|
|
219
|
+
else {
|
|
220
|
+
selfTemplateVariables = {
|
|
221
|
+
$self: _getRowTemplateVariables(this.table, context, mainTuple.linkedData[this.name]),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// any other paths
|
|
226
|
+
else if (baseCol) {
|
|
227
|
+
selfTemplateVariables = {
|
|
228
|
+
$self: baseCol.formatvalue(mainTuple.data[baseCol.name], context),
|
|
229
|
+
$_self: mainTuple.data[baseCol.name],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const keyValues = {};
|
|
234
|
+
Object.assign(keyValues, templateVariables, selfTemplateVariables);
|
|
235
|
+
return processMarkdownPattern(this.display.sourceMarkdownPattern, keyValues, this.table, context, {
|
|
236
|
+
templateEngine: this.display.sourceTemplateEngine,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// aggregate
|
|
241
|
+
if (this.hasAggregate) {
|
|
242
|
+
const nullValue = this._getNullValue(context);
|
|
243
|
+
return columnValue ? columnValue : { isHTML: false, value: nullValue, unformatted: nullValue };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// all outbound
|
|
247
|
+
if (this.hasPath && this.isUnique) {
|
|
248
|
+
return this.formatPresentation(mainTuple.linkedData[this.name], mainTuple.page.reference.context, null, { skipWaitFor: true });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// other cases
|
|
252
|
+
return super.sourceFormatPresentation(templateVariables, columnValue, mainTuple);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Returns a promise that gets resolved with list of aggregated values in the same
|
|
257
|
+
* order of tuples of the page that is passed.
|
|
258
|
+
* Each returned value has the following attributes:
|
|
259
|
+
* - value
|
|
260
|
+
* - isHTML
|
|
261
|
+
* - templateVariables: the template variables that the client uses to eventually pass to sourceFormatPresentation
|
|
262
|
+
*
|
|
263
|
+
* implementation Notes:
|
|
264
|
+
* 1. This function will take care of url limitation. It might generate multiple
|
|
265
|
+
* ermrest requests based on the url length, and will resolve the promise when
|
|
266
|
+
* all the requests have been succeeded. If we cannot fit all the requests, an
|
|
267
|
+
* error will be thrown.
|
|
268
|
+
* 2. Only in case of entity scalar aggregate we are going to get all the row data.
|
|
269
|
+
* In other cases, the returned data will only include the scalar value.
|
|
270
|
+
* 3. Regarding the returned value:
|
|
271
|
+
* 3.0. Null and empty string values are treated the same way as any array column.
|
|
272
|
+
* We are going to show the special value for them.
|
|
273
|
+
* 3.1. If it's an array aggregate:
|
|
274
|
+
* 3.1.1. array_display will dictate how we should join the values (csv, olist, ulist, raw).
|
|
275
|
+
* 3.1.2. array_options will dictate the sort and length criteria.
|
|
276
|
+
* 3.1.3. Based on entity/scalar mode:
|
|
277
|
+
* 3.1.3.1. In scalar mode, only pre_format will be applied to each value.
|
|
278
|
+
* 3.1.3.2. In entity mode, we are going to return list of row_names derived from `row_name/compact`.
|
|
279
|
+
* 3.2. Otherwise we will only apply the pre_format annotation for the column.
|
|
280
|
+
*
|
|
281
|
+
* @param {Page} page the page object of main (current) refernece
|
|
282
|
+
* @param {Object} contextHeaderParams the object that we want to log.
|
|
283
|
+
* @return {Promise}
|
|
284
|
+
*/
|
|
285
|
+
getAggregatedValue(page: Page, contextHeaderParams?: any): Promise<{ value: any; isHTML: boolean; templateVariables: any }[]> {
|
|
286
|
+
return new Promise((resolve, reject) => {
|
|
287
|
+
const values: { value: any; isHTML: boolean; templateVariables: any }[] = [];
|
|
288
|
+
const mainTable = this._currentTable;
|
|
289
|
+
const location = this._baseReference.location;
|
|
290
|
+
const http = this._baseReference.server.http;
|
|
291
|
+
const column = this.baseColumns[0];
|
|
292
|
+
let pathToCol: string;
|
|
293
|
+
|
|
294
|
+
// this will dictates whether we should show rowname or not
|
|
295
|
+
const aggFn = this.sourceObject.aggregate;
|
|
296
|
+
const isRow = this.isEntityMode && _pseudoColEntityAggregateFns.indexOf(aggFn) !== -1;
|
|
297
|
+
|
|
298
|
+
// verify the input
|
|
299
|
+
try {
|
|
300
|
+
verify(this.hasAggregate, 'this function should only be used when `hasAggregate` is true.');
|
|
301
|
+
verify(page && page.reference.table === mainTable, 'given page object must be defined and from the base table.');
|
|
302
|
+
} catch (e) {
|
|
303
|
+
reject(e);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// create the header
|
|
308
|
+
if (!contextHeaderParams || !isObject(contextHeaderParams)) {
|
|
309
|
+
contextHeaderParams = { action: 'read/aggregate' };
|
|
310
|
+
}
|
|
311
|
+
const config = {
|
|
312
|
+
headers: this.reference._generateContextHeader(contextHeaderParams),
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// return empty list if page is empty
|
|
316
|
+
if (page.tuples.length === 0) {
|
|
317
|
+
resolve(values);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// make sure table has shortestkey of length 1
|
|
322
|
+
if (mainTable.shortestKey.length > 1) {
|
|
323
|
+
$log.warn('This function only works with tables that have at least a simple key.');
|
|
324
|
+
resolve(values);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const currTable = 'T';
|
|
329
|
+
const baseTable = this.hasPath ? 'M' : currTable;
|
|
330
|
+
|
|
331
|
+
const keyColName = mainTable.shortestKey[0].name;
|
|
332
|
+
const keyColNameEncoded = fixedEncodeURIComponent(mainTable.shortestKey[0].name);
|
|
333
|
+
const projection = `/c:=:${baseTable}:${keyColNameEncoded};v:=${aggFn}(${currTable}:${isRow ? '*' : fixedEncodeURIComponent(column.name)})`;
|
|
334
|
+
|
|
335
|
+
// generate the base path in the following format:
|
|
336
|
+
// <baseUri><basePath><filters><path-to-pseudo-col><projection>
|
|
337
|
+
// the following shows where `/` is stored for each part:
|
|
338
|
+
// <baseUri/><basePath/><filters></path-from-main-to-pseudo-col></projection>
|
|
339
|
+
const baseUri = [location.service, 'catalog', location.catalog, 'attributegroup'].join('/') + '/';
|
|
340
|
+
const basePath = baseTable + ':=' + fixedEncodeURIComponent(mainTable.schema.name) + ':' + fixedEncodeURIComponent(mainTable.name) + '/';
|
|
341
|
+
pathToCol = this.sourceObjectWrapper!.toString(false, false, currTable);
|
|
342
|
+
if (pathToCol.length > 0) {
|
|
343
|
+
pathToCol = '/' + pathToCol;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// make sure just projection and base uri doesn't go over limit.
|
|
347
|
+
if (basePath.length + pathToCol.length + projection.length >= URL_PATH_LENGTH_LIMIT) {
|
|
348
|
+
$log.warn("couldn't generate the requests because of url limitation");
|
|
349
|
+
resolve(values);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// get the computed filters
|
|
354
|
+
const keyValueRes = generateKeyValueFilters(
|
|
355
|
+
mainTable.shortestKey,
|
|
356
|
+
page.tuples.map((t) => t.data),
|
|
357
|
+
mainTable.schema.catalog,
|
|
358
|
+
(basePath + pathToCol + projection).length,
|
|
359
|
+
mainTable.displayname.value,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
if (!keyValueRes.successful || !keyValueRes.filters) {
|
|
363
|
+
$log.warn(keyValueRes.message);
|
|
364
|
+
resolve(values);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// turn the paths into requests
|
|
369
|
+
const httpPromises = keyValueRes.filters.map((f) => {
|
|
370
|
+
return http.get(baseUri + basePath + f.path + pathToCol + projection, config);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// if adding any of the filters would go over url limit
|
|
374
|
+
if (httpPromises.length === 0) {
|
|
375
|
+
$log.warn("couldn't generate the requests because of url limitation");
|
|
376
|
+
resolve(values);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
Promise.all(httpPromises)
|
|
381
|
+
.then((response) => {
|
|
382
|
+
const result: any[] = [];
|
|
383
|
+
let responseValues: any[] = [];
|
|
384
|
+
let value: any;
|
|
385
|
+
|
|
386
|
+
response.forEach((r: any) => {
|
|
387
|
+
responseValues = responseValues.concat(r.data);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// make sure we're returning the result in the same order as input
|
|
391
|
+
page.tuples.forEach((t) => {
|
|
392
|
+
// find the corresponding value in result
|
|
393
|
+
value = responseValues.find((v) => {
|
|
394
|
+
return v.c === t.data[keyColName];
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
result.push(processAggregateValue(value && value.v ? value.v : null, this, aggFn, isRow));
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
resolve(result);
|
|
401
|
+
})
|
|
402
|
+
.catch((err) => {
|
|
403
|
+
reject(ErrorService.responseToError(err));
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
protected _determineSortable(): void {
|
|
409
|
+
this._sortColumns_cached = [];
|
|
410
|
+
this._sortable = false;
|
|
411
|
+
|
|
412
|
+
// disable sort if it has aggregate
|
|
413
|
+
if (this.hasAggregate) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (this.isUnique) {
|
|
418
|
+
if (this.isEntityMode) {
|
|
419
|
+
const fk = this.lastForeignKeyNode!.nodeObject;
|
|
420
|
+
const display = fk.getDisplay(this._context);
|
|
421
|
+
|
|
422
|
+
// disable the sort
|
|
423
|
+
if (display !== undefined && display.columnOrder === false) return;
|
|
424
|
+
|
|
425
|
+
// use the column_order
|
|
426
|
+
if (display !== undefined && display.columnOrder !== undefined && display.columnOrder.length !== 0) {
|
|
427
|
+
this._sortColumns_cached = display.columnOrder;
|
|
428
|
+
this._sortable = true;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (this.reference.display._rowOrder !== undefined) {
|
|
433
|
+
this._sortColumns_cached = this.reference.display._rowOrder;
|
|
434
|
+
this._sortable = true;
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// use the column's
|
|
440
|
+
this._sortColumns_cached = this.baseColumns[0]._getSortColumns(this._context); //might return undefined
|
|
441
|
+
|
|
442
|
+
if (typeof this._sortColumns_cached === 'undefined') {
|
|
443
|
+
this._sortColumns_cached = [];
|
|
444
|
+
} else {
|
|
445
|
+
this._sortable = true;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
451
|
+
protected _determineInputDisabled(context: string): boolean | { message: string } {
|
|
452
|
+
throw new Error('can not use this type of column in entry mode.');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* If the first foreign key is outbound, this function will return the value that this pseudo-column represents.
|
|
457
|
+
*
|
|
458
|
+
* The first fk must be outbound because the path is generated from the table that the first fk refers to. This is
|
|
459
|
+
* useful for fetching the values of wait-fors in the entry contexts. Since the main record might not be available yet,
|
|
460
|
+
* we're starting from the fk table.
|
|
461
|
+
*
|
|
462
|
+
* Ideally this and getAggregatedValue should be merged, these are the differences:
|
|
463
|
+
* - for this function agg fn is not required.
|
|
464
|
+
* - this function requires the first fk to be outbound, while getAggregatedValue doesn't.
|
|
465
|
+
* - the generated path here ignores the first hop.
|
|
466
|
+
* - if the key value for any of the rows is null, this will only ignore that row (getAggregatedValue will just give up and return empty).
|
|
467
|
+
*
|
|
468
|
+
* @param {any} data the submission data
|
|
469
|
+
* @param {Record<string, any>} contextHeaderParams
|
|
470
|
+
*/
|
|
471
|
+
getFirstOutboundValue(data: any[], contextHeaderParams?: Record<string, any>): Promise<any[]> {
|
|
472
|
+
return new Promise((resolve, reject) => {
|
|
473
|
+
const encode = fixedEncodeURIComponent;
|
|
474
|
+
const location = this._baseReference.location;
|
|
475
|
+
const http = this._baseReference.server.http;
|
|
476
|
+
|
|
477
|
+
// these are the same checks as the processWaitFor in the entry context. since that's the only usecase of this for now.
|
|
478
|
+
const sw = this.sourceObjectWrapper!;
|
|
479
|
+
const firstFk = this.firstForeignKeyNode ? this.firstForeignKeyNode.nodeObject : null;
|
|
480
|
+
if (firstFk === undefined || this.firstForeignKeyNode!.isInbound) {
|
|
481
|
+
$log.warn('This function should only be used when the first foreign key is outbound.');
|
|
482
|
+
resolve([]);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (sw.foreignKeyPathLength < 2 || sw.hasPrefix || (sw.isFiltered && sw.filterProps && sw.filterProps.hasRootFilter)) {
|
|
486
|
+
$log.warn('This function only support paths that start with outbound, have no filter or prefix, and at least 2 fk hops.');
|
|
487
|
+
resolve([]);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// create the header
|
|
492
|
+
if (!contextHeaderParams || !isObject(contextHeaderParams)) {
|
|
493
|
+
contextHeaderParams = { action: 'read/outbound' };
|
|
494
|
+
}
|
|
495
|
+
const config = {
|
|
496
|
+
headers: this.reference._generateContextHeader(contextHeaderParams),
|
|
497
|
+
};
|
|
498
|
+
// if agg is missing, we're getting the rows
|
|
499
|
+
const aggFn = this.sourceObject.aggregate ? this.sourceObject.aggregate : 'array_d';
|
|
500
|
+
const isRow = this.isEntityMode && _pseudoColEntityAggregateFns.indexOf(aggFn) !== -1;
|
|
501
|
+
const isAllOutbound = this.isPathColumn && this.hasPath && this.isUnique && !this.hasAggregate;
|
|
502
|
+
|
|
503
|
+
// the table that the path starts with
|
|
504
|
+
const baseTable = firstFk.key.table;
|
|
505
|
+
const baseTableKeyColumns = firstFk.key.colset.columns;
|
|
506
|
+
|
|
507
|
+
const currTableAlias = 'T';
|
|
508
|
+
const baseTableAlias = 'M';
|
|
509
|
+
let aliasUsedForProjectedValue = 'v';
|
|
510
|
+
let num = 1;
|
|
511
|
+
while (baseTableKeyColumns.some((c: any) => c.name === aliasUsedForProjectedValue)) {
|
|
512
|
+
aliasUsedForProjectedValue = 'v' + num++;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const column = this.baseColumns[0];
|
|
516
|
+
let projection =
|
|
517
|
+
'/' +
|
|
518
|
+
baseTableKeyColumns
|
|
519
|
+
.map((c: any) => {
|
|
520
|
+
return `${baseTableAlias}:${encode(c.name)}`;
|
|
521
|
+
})
|
|
522
|
+
.join(',');
|
|
523
|
+
projection += `;${aliasUsedForProjectedValue}:=${aggFn}(${currTableAlias}:${isRow ? '*' : encode(column.name)})`;
|
|
524
|
+
|
|
525
|
+
const lastFk = this.lastForeignKeyNode;
|
|
526
|
+
const baseUri = `${location.service}/catalog/${location.catalog}/attributegroup/`;
|
|
527
|
+
const basePath = `${baseTableAlias}:=${encode(baseTable.schema.name)}:${encode(baseTable.name)}/`;
|
|
528
|
+
|
|
529
|
+
// NOTE we're not allowing first hop filter or path filter for this case, so the following is assuming that
|
|
530
|
+
const pathToCol = this.sourceObjectWrapper!.sourceObjectNodes!.reduce((acc: string, sn: SourceObjectNode, index: number) => {
|
|
531
|
+
// ignoring the first hop
|
|
532
|
+
if (index === 0) return '/';
|
|
533
|
+
// add alias to the last hop
|
|
534
|
+
const addAlias = sn === lastFk;
|
|
535
|
+
return acc + (index > 1 ? '/' : '') + (addAlias ? `${currTableAlias}:=` : '') + sn.toString();
|
|
536
|
+
}, '');
|
|
537
|
+
|
|
538
|
+
// make sure just projection and base uri doesn't go over limit.
|
|
539
|
+
if ((basePath + pathToCol + projection).length >= URL_PATH_LENGTH_LIMIT) {
|
|
540
|
+
$log.warn("couldn't generate the requests because of url limitation");
|
|
541
|
+
resolve([]);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// get the computed filters
|
|
546
|
+
const keyData: any[] = [];
|
|
547
|
+
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
|
|
548
|
+
const temp: any = {};
|
|
549
|
+
let hasNull = false;
|
|
550
|
+
for (let colIndex = 0; colIndex < firstFk.colset.columns.length; colIndex++) {
|
|
551
|
+
const c = firstFk.colset.columns[colIndex];
|
|
552
|
+
if (isDefinedAndNotNull(data[rowIndex][c.name])) {
|
|
553
|
+
temp[firstFk.mapping.get(c).name] = data[rowIndex][c.name];
|
|
554
|
+
} else {
|
|
555
|
+
hasNull = true;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// ignore the row if any of the key values is null
|
|
560
|
+
if (!hasNull) {
|
|
561
|
+
keyData.push(temp);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const keyValueRes = generateKeyValueFilters(
|
|
565
|
+
baseTableKeyColumns,
|
|
566
|
+
keyData,
|
|
567
|
+
baseTable.schema.catalog,
|
|
568
|
+
(basePath + pathToCol + projection).length,
|
|
569
|
+
baseTable.displayname.value,
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
if (!keyValueRes.successful || !keyValueRes.filters) {
|
|
573
|
+
$log.warn(keyValueRes.message);
|
|
574
|
+
resolve([]);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// turn the paths into request
|
|
579
|
+
const promises = keyValueRes.filters.map((f: any) => {
|
|
580
|
+
return http.get(baseUri + basePath + f.path + pathToCol + projection, config);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// if adding any of the filters would go over url limit
|
|
584
|
+
if (promises.length === 0) {
|
|
585
|
+
$log.warn("couldn't generate the requests because of url limitation");
|
|
586
|
+
resolve([]);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
Promise.all(promises)
|
|
591
|
+
.then((response) => {
|
|
592
|
+
const result: any[] = [];
|
|
593
|
+
let values: any[] = [];
|
|
594
|
+
response.forEach((r: any) => {
|
|
595
|
+
values = values.concat(r.data);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
data.forEach((d) => {
|
|
599
|
+
// find the value corresponding to the current tuple
|
|
600
|
+
const value = values.find((v) => {
|
|
601
|
+
return baseTableKeyColumns.every((c: any) => {
|
|
602
|
+
return v[c.name] === d[firstFk.mapping.getFromColumn(c).name];
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const presValue = processAggregateValue(value ? value[aliasUsedForProjectedValue] : null, this, aggFn, isRow);
|
|
607
|
+
// if it's alloutbound and agg fn was missing, we added array_d so we can send the request,
|
|
608
|
+
// so make sure the returned value is not actually array
|
|
609
|
+
if (isAllOutbound && !this.sourceObject.aggregate) {
|
|
610
|
+
if ('$self' in presValue.templateVariables && Array.isArray(presValue.templateVariables.$self)) {
|
|
611
|
+
presValue.templateVariables.$self = presValue.templateVariables.$self[0];
|
|
612
|
+
}
|
|
613
|
+
if ('$_self' in presValue.templateVariables && Array.isArray(presValue.templateVariables.$_self)) {
|
|
614
|
+
presValue.templateVariables.$_self = presValue.templateVariables.$_self[0];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
result.push(presValue);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
resolve(result);
|
|
622
|
+
})
|
|
623
|
+
.catch((err) => {
|
|
624
|
+
reject(ErrorService.responseToError(err));
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Private cached properties for getters
|
|
630
|
+
private _key?: any;
|
|
631
|
+
private _aggregateFn?: string | null;
|
|
632
|
+
private _reference?: Reference;
|
|
633
|
+
private _canUseScalarProjection?: boolean;
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* The tooltip that should be used for this column.
|
|
637
|
+
* It will return the first applicable rule:
|
|
638
|
+
* 1. comment that is defined on the sourceObject, use it.
|
|
639
|
+
* 2. if aggregate and scalar use the "<function> <col_displayname>"
|
|
640
|
+
* 3. if aggregate and entity use the "<function> <table_displayname>"
|
|
641
|
+
* 3. In entity mode, return the table's displayname.
|
|
642
|
+
* 4. In scalar return the column's displayname.
|
|
643
|
+
*/
|
|
644
|
+
get comment(): CommentType {
|
|
645
|
+
if (this._comment === undefined) {
|
|
646
|
+
const getComment = (self: PseudoColumn): any => {
|
|
647
|
+
if (self.hasAggregate) {
|
|
648
|
+
// if defined on the sourceObject use it.
|
|
649
|
+
const com = _processSourceObjectComment(self.sourceObject);
|
|
650
|
+
if (com) {
|
|
651
|
+
return com;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// otherwise generate one
|
|
655
|
+
const agIndex = _pseudoColAggregateFns.indexOf(self.sourceObject.aggregate);
|
|
656
|
+
let dname = self.baseColumns[0].displayname.unformatted;
|
|
657
|
+
if (self.isEntityMode) {
|
|
658
|
+
dname = self.baseColumns[0].table.displayname.unformatted;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return _processModelComment([_pseudoColAggregateExplicitName[agIndex], dname].join(' '), false);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// if it's not aggregate, we can get it from the table or column depending on entity mode:
|
|
665
|
+
let disp: any, commentDisplayMode: any;
|
|
666
|
+
if (!self.isEntityMode) {
|
|
667
|
+
disp = self.baseColumns[0].getDisplay(self._context);
|
|
668
|
+
commentDisplayMode = disp.commentDisplayMode;
|
|
669
|
+
} else {
|
|
670
|
+
disp = self.table.getDisplay(self._context);
|
|
671
|
+
commentDisplayMode = disp.tableCommentDisplayMode;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return _processSourceObjectComment(
|
|
675
|
+
self.sourceObject,
|
|
676
|
+
disp.comment ? disp.comment.unformatted : null,
|
|
677
|
+
disp.commentRenderMarkdown,
|
|
678
|
+
commentDisplayMode,
|
|
679
|
+
);
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
this._comment = getComment(this);
|
|
683
|
+
}
|
|
684
|
+
return this._comment!;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* The tooltip that should be used for this column.
|
|
689
|
+
* It will return the first applicable rule:
|
|
690
|
+
* 1. comment that is defined on the sourceObject, use it.
|
|
691
|
+
* 2. if aggregate and scalar use the "<function> <col_displayname>"
|
|
692
|
+
* 3. if aggregate and entity use the "<function> <table_displayname>"
|
|
693
|
+
* 3. In entity mode, return the table's displayname.
|
|
694
|
+
* 4. In scalar return the column's displayname.
|
|
695
|
+
*/
|
|
696
|
+
get displayname(): DisplayName {
|
|
697
|
+
if (this._displayname === undefined) {
|
|
698
|
+
const attachDisplayname = (self: PseudoColumn): void => {
|
|
699
|
+
if (self.sourceObject.markdown_name) {
|
|
700
|
+
self._displayname = {
|
|
701
|
+
value: renderMarkdown(self.sourceObject.markdown_name, true),
|
|
702
|
+
unformatted: self.sourceObject.markdown_name,
|
|
703
|
+
isHTML: true,
|
|
704
|
+
};
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (self.hasAggregate) {
|
|
709
|
+
// actual displayname
|
|
710
|
+
void super.displayname; // this will set the _displayname if it wasn't set before
|
|
711
|
+
const displayname = self.isEntityMode ? self.baseColumns[0].table.displayname : self._displayname!;
|
|
712
|
+
|
|
713
|
+
// prefix
|
|
714
|
+
const agIndex = _pseudoColAggregateFns.indexOf(self.sourceObject.aggregate);
|
|
715
|
+
const name = _pseudoColAggregateNames[agIndex];
|
|
716
|
+
|
|
717
|
+
self._displayname = {
|
|
718
|
+
value: name ? [name, displayname.value].join(' ') : displayname.value,
|
|
719
|
+
unformatted: name ? [name, displayname.unformatted].join(' ') : displayname.unformatted,
|
|
720
|
+
isHTML: displayname.isHTML,
|
|
721
|
+
};
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (!self.isEntityMode) {
|
|
726
|
+
Object.getOwnPropertyDescriptor(ReferenceColumn.prototype, 'displayname')!.get!.call(self);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// displayname of the table.
|
|
731
|
+
self._displayname = self.baseColumns[0].table.displayname;
|
|
732
|
+
return;
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
attachDisplayname(this);
|
|
736
|
+
}
|
|
737
|
+
return this._displayname!;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* If the pseudoColumn is in entity mode will return the key that this column represents
|
|
742
|
+
*/
|
|
743
|
+
get key(): Key | null {
|
|
744
|
+
if (this._key === undefined) {
|
|
745
|
+
this._key = this.isEntityMode ? this.baseColumn.uniqueNotNullKey : null;
|
|
746
|
+
}
|
|
747
|
+
return this._key;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
get aggregateFn(): string | null {
|
|
751
|
+
if (this._aggregateFn === undefined) {
|
|
752
|
+
this._aggregateFn = this.hasAggregate ? this.sourceObject.aggregate : null;
|
|
753
|
+
}
|
|
754
|
+
return this._aggregateFn!;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Returns a reference to the current pseudo-column
|
|
759
|
+
* This is how it behaves:
|
|
760
|
+
* 1. If pseudo-column has no path, it will return the base reference.
|
|
761
|
+
* 3. if mainTuple is available, create the reference based on this path:
|
|
762
|
+
* <pseudoColumnSchema:PseudoColumnTable>/<path from pseudo-column to main table>/<facets based on value of shortestkey of main table>
|
|
763
|
+
* 4. Otherwise create the path by traversing the path
|
|
764
|
+
*/
|
|
765
|
+
get reference(): Reference | RelatedReference {
|
|
766
|
+
if (this._reference === undefined) {
|
|
767
|
+
if (!this.hasPath) {
|
|
768
|
+
this._reference = this._baseReference.copy(undefined, undefined, this);
|
|
769
|
+
} else {
|
|
770
|
+
let facet: unknown;
|
|
771
|
+
if (this._mainTuple) {
|
|
772
|
+
facet = this.sourceObjectWrapper!.getReverseAsFacet(this._mainTuple, this._baseReference.table);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// if data didn't exist, we should traverse the path
|
|
776
|
+
let uri = this.table.uri;
|
|
777
|
+
if (!isObjectAndNotNull(facet)) {
|
|
778
|
+
uri = this._baseReference.location.compactUri + '/' + this.sourceObjectWrapper!.toString(false, false);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// this._reference = new Reference(parse(uri), this.table.schema.catalog, this.displayname, this.comment, this);
|
|
782
|
+
this._reference = new RelatedReference(
|
|
783
|
+
parse(uri),
|
|
784
|
+
this.table.schema.catalog,
|
|
785
|
+
this._baseReference.table,
|
|
786
|
+
this.firstForeignKeyNode!.nodeObject,
|
|
787
|
+
[],
|
|
788
|
+
[],
|
|
789
|
+
this.compressedDataSource,
|
|
790
|
+
undefined,
|
|
791
|
+
this.displayname,
|
|
792
|
+
this.comment,
|
|
793
|
+
this,
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
// make sure data exists
|
|
797
|
+
if (isObjectAndNotNull(facet)) {
|
|
798
|
+
this._reference.location.facets = facet;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return this._reference;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
set reference(ref: Reference) {
|
|
806
|
+
//TODO this should be revisited, chaise is mutating the reference!
|
|
807
|
+
this._reference = ref;
|
|
808
|
+
}
|
|
809
|
+
get default(): unknown {
|
|
810
|
+
throw new Error('can not use this type of column in entry mode.');
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
get nullok(): boolean {
|
|
814
|
+
throw new Error('can not use this type of column in entry mode.');
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Whether we can use the raw column in the projection list or not.
|
|
819
|
+
*
|
|
820
|
+
* If we only need the value of scalar column and none of the other columns of the
|
|
821
|
+
* all-outbound path then we can simply use the scalar projection.
|
|
822
|
+
* Therefore the pseudo-column must:
|
|
823
|
+
* - be all-outbound path in scalar mode
|
|
824
|
+
* - the leaf column cannot have any column_display annotation
|
|
825
|
+
* - the leaf column cannot be sorted or doesn't have a sort based on other columns of the table.
|
|
826
|
+
*/
|
|
827
|
+
get canUseScalarProjection(): boolean {
|
|
828
|
+
if (this._canUseScalarProjection === undefined) {
|
|
829
|
+
const populate = (self: PseudoColumn): boolean => {
|
|
830
|
+
// only in scalar mode
|
|
831
|
+
if (self.isEntityMode || !self.isUnique) {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
// if it has column_display we cannot use scalar
|
|
835
|
+
if (self.baseColumn.getDisplay(self._context).isMarkdownPattern) {
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
// if it's sortable and based on other columns, we cannot use scalar
|
|
839
|
+
const sortCols = (self as any)._sortColumns;
|
|
840
|
+
if (self.sortable && (sortCols.length !== 1 || sortCols[0].column.name !== self.baseColumn.name)) {
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return true;
|
|
845
|
+
};
|
|
846
|
+
this._canUseScalarProjection = populate(this);
|
|
847
|
+
}
|
|
848
|
+
return this._canUseScalarProjection;
|
|
849
|
+
}
|
|
850
|
+
}
|