@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.
Files changed (71) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +55 -0
  3. package/dist/ermrest.d.ts +3481 -0
  4. package/dist/ermrest.js +45 -0
  5. package/dist/ermrest.js.gz +0 -0
  6. package/dist/ermrest.js.map +1 -0
  7. package/dist/ermrest.min.js +45 -0
  8. package/dist/ermrest.min.js.gz +0 -0
  9. package/dist/ermrest.min.js.map +1 -0
  10. package/dist/ermrest.ver.txt +1 -0
  11. package/dist/stats.html +4949 -0
  12. package/js/ag_reference.js +1483 -0
  13. package/js/core.js +4931 -0
  14. package/js/datapath.js +336 -0
  15. package/js/export.js +956 -0
  16. package/js/filters.js +192 -0
  17. package/js/format.js +344 -0
  18. package/js/hatrac.js +1130 -0
  19. package/js/json_ld_validator.js +285 -0
  20. package/js/parser.js +2320 -0
  21. package/js/setup/node.js +27 -0
  22. package/js/utils/helpers.js +2300 -0
  23. package/js/utils/json_ld_schema.js +680 -0
  24. package/js/utils/pseudocolumn_helpers.js +2196 -0
  25. package/package.json +79 -0
  26. package/src/index.ts +204 -0
  27. package/src/models/comment.ts +14 -0
  28. package/src/models/deferred-promise.ts +16 -0
  29. package/src/models/display-name.ts +5 -0
  30. package/src/models/errors.ts +408 -0
  31. package/src/models/path-prefix-alias-mapping.ts +130 -0
  32. package/src/models/reference/bulk-create-foreign-key-object.ts +133 -0
  33. package/src/models/reference/citation.ts +98 -0
  34. package/src/models/reference/contextualize.ts +535 -0
  35. package/src/models/reference/google-dataset-metadata.ts +72 -0
  36. package/src/models/reference/index.ts +14 -0
  37. package/src/models/reference/page.ts +520 -0
  38. package/src/models/reference/reference-aggregate-fn.ts +37 -0
  39. package/src/models/reference/reference.ts +2813 -0
  40. package/src/models/reference/related-reference.ts +467 -0
  41. package/src/models/reference/tuple.ts +652 -0
  42. package/src/models/reference-column/asset-pseudo-column.ts +498 -0
  43. package/src/models/reference-column/column-aggregate.ts +313 -0
  44. package/src/models/reference-column/facet-column.ts +1380 -0
  45. package/src/models/reference-column/foreign-key-pseudo-column.ts +626 -0
  46. package/src/models/reference-column/inbound-foreign-key-pseudo-column.ts +131 -0
  47. package/src/models/reference-column/index.ts +13 -0
  48. package/src/models/reference-column/key-pseudo-column.ts +236 -0
  49. package/src/models/reference-column/pseudo-column.ts +850 -0
  50. package/src/models/reference-column/reference-column.ts +740 -0
  51. package/src/models/source-object-node.ts +156 -0
  52. package/src/models/source-object-wrapper.ts +694 -0
  53. package/src/models/table-source-definitions.ts +98 -0
  54. package/src/services/authn.ts +43 -0
  55. package/src/services/catalog.ts +37 -0
  56. package/src/services/config.ts +202 -0
  57. package/src/services/error.ts +247 -0
  58. package/src/services/handlebars.ts +607 -0
  59. package/src/services/history.ts +136 -0
  60. package/src/services/http.ts +536 -0
  61. package/src/services/logger.ts +70 -0
  62. package/src/services/mustache.ts +0 -0
  63. package/src/utils/column-utils.ts +308 -0
  64. package/src/utils/constants.ts +526 -0
  65. package/src/utils/markdown-utils.ts +855 -0
  66. package/src/utils/reference-utils.ts +1658 -0
  67. package/src/utils/template-utils.ts +0 -0
  68. package/src/utils/type-utils.ts +89 -0
  69. package/src/utils/value-utils.ts +127 -0
  70. package/tsconfig.json +30 -0
  71. package/vite.config.mts +104 -0
@@ -0,0 +1,98 @@
1
+ import type { Reference, Tuple, VisibleColumn } from '@isrd-isi-edu/ermrestjs/src/models/reference';
2
+
3
+ import { _renderTemplate } from '@isrd-isi-edu/ermrestjs/js/utils/helpers';
4
+ import { _processWaitForList } from '@isrd-isi-edu/ermrestjs/js/utils/pseudocolumn_helpers';
5
+
6
+ export interface CitationObject {
7
+ author?: string | null;
8
+ title?: string | null;
9
+ journal?: string | null;
10
+ year?: string | null;
11
+ url?: string | null;
12
+ id?: string | null;
13
+ }
14
+
15
+ export interface CitationAnnotation {
16
+ journal_pattern?: string;
17
+ year_pattern?: string;
18
+ url_pattern?: string;
19
+ author_pattern?: string;
20
+ title_pattern?: string;
21
+ id_pattern?: string;
22
+ template_engine?: string;
23
+ wait_for?: any;
24
+ }
25
+
26
+ /**
27
+ * Constructs a citation for the given tuple.
28
+ * The given citationAnnotation must be valid and have the appropriate variables.
29
+ */
30
+ export class Citation {
31
+ private _table: any;
32
+ private _citationAnnotation: CitationAnnotation;
33
+
34
+ public waitFor: VisibleColumn[];
35
+ public hasWaitFor: boolean;
36
+ public hasWaitForAggregate: boolean;
37
+
38
+ constructor(reference: Reference, citationAnnotation: CitationAnnotation) {
39
+ this._table = reference.table;
40
+
41
+ /**
42
+ * citation specific properties include:
43
+ * - journal*
44
+ * - author
45
+ * - title
46
+ * - year*
47
+ * - url*
48
+ * - id
49
+ * other properties:
50
+ * - template_engine
51
+ * - wait_for
52
+ */
53
+ this._citationAnnotation = citationAnnotation;
54
+
55
+ const waitForRes = _processWaitForList(citationAnnotation.wait_for, reference, reference.table, null, null, 'citation');
56
+
57
+ this.waitFor = waitForRes.waitForList;
58
+ this.hasWaitFor = waitForRes.hasWaitFor;
59
+ this.hasWaitForAggregate = waitForRes.hasWaitForAggregate;
60
+ }
61
+
62
+ /**
63
+ * Given the templateVariables variables, will generate the citaiton.
64
+ * @param tuple - the tuple object that this citaiton is based on
65
+ * @param templateVariables - if it's not an obect, we will use the tuple templateVariables
66
+ * @return if the returned template for required attributes are empty, it will return null.
67
+ */
68
+ compute(tuple: Tuple, templateVariables?: any): CitationObject | null {
69
+ const table = this._table;
70
+ const citationAnno = this._citationAnnotation;
71
+
72
+ // make sure required parameters are present
73
+ if (!citationAnno.journal_pattern || !citationAnno.year_pattern || !citationAnno.url_pattern) {
74
+ return null;
75
+ }
76
+
77
+ if (!templateVariables) {
78
+ templateVariables = tuple.templateVariables.values;
79
+ }
80
+
81
+ const keyValues = Object.assign({ $self: tuple.selfTemplateVariable }, templateVariables);
82
+
83
+ const citation: CitationObject = {};
84
+ // author, title, id set to null if not defined
85
+ (['author', 'title', 'journal', 'year', 'url', 'id'] as const).forEach((key) => {
86
+ citation[key] = _renderTemplate(citationAnno[`${key}_pattern`]!, keyValues, table.schema.catalog, {
87
+ templateEngine: citationAnno.template_engine,
88
+ });
89
+ });
90
+
91
+ // if after processing the templates, any of the required fields are null, template is invalid
92
+ if (!citation.journal || !citation.year || !citation.url) {
93
+ return null;
94
+ }
95
+
96
+ return citation;
97
+ }
98
+ }
@@ -0,0 +1,535 @@
1
+ // services
2
+ import CatalogSerivce from '@isrd-isi-edu/ermrestjs/src/services/catalog';
3
+
4
+ // models
5
+ import { type Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
6
+
7
+ // utils
8
+ import { _constraintTypes, _contexts, FILTER_TYPES } from '@isrd-isi-edu/ermrestjs/src/utils/constants';
9
+ import { fixedEncodeURIComponent } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
10
+ import { isStringAndNotEmpty } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
11
+ import { _FacetsLogicalOperators } from '@isrd-isi-edu/ermrestjs/src/utils/constants';
12
+
13
+ // legacy
14
+ import { parse } from '@isrd-isi-edu/ermrestjs/js/parser';
15
+ import { _sourceColumnHelpers } from '@isrd-isi-edu/ermrestjs/js/utils/pseudocolumn_helpers';
16
+
17
+ /**
18
+ * Contructs the Contextualize object.
19
+ *
20
+ * Usage:
21
+ * Clients _do not_ directly access this constructor.
22
+ * See {@link Reference#contextualize}
23
+ *
24
+ * It will be used for creating contextualized references.
25
+ */
26
+ export class Contextualize {
27
+ private _reference: Reference;
28
+
29
+ constructor(reference: Reference) {
30
+ this._reference = reference;
31
+ }
32
+
33
+ /**
34
+ * The _record_ context of this reference.
35
+ */
36
+ get detailed(): Reference {
37
+ return this._contextualize(_contexts.DETAILED);
38
+ }
39
+
40
+ /**
41
+ * The _compact_ context of this reference.
42
+ */
43
+ get compact(): Reference {
44
+ return this._contextualize(_contexts.COMPACT);
45
+ }
46
+
47
+ /**
48
+ * The _compact/brief_ context of this reference.
49
+ */
50
+ get compactBrief(): Reference {
51
+ return this._contextualize(_contexts.COMPACT_BRIEF);
52
+ }
53
+
54
+ /**
55
+ * The _compact/select_ context of this reference.
56
+ */
57
+ get compactSelect(): Reference {
58
+ return this._contextualize(_contexts.COMPACT_SELECT);
59
+ }
60
+
61
+ /**
62
+ * The _compact/select/association_ context of this reference.
63
+ */
64
+ get compactSelectAssociation(): Reference {
65
+ return this._contextualize(_contexts.COMPACT_SELECT_ASSOCIATION);
66
+ }
67
+
68
+ /**
69
+ * The _compact/select/association/link_ context of this reference.
70
+ */
71
+ get compactSelectAssociationLink(): Reference {
72
+ return this._contextualize(_contexts.COMPACT_SELECT_ASSOCIATION_LINK);
73
+ }
74
+
75
+ /**
76
+ * The _compact/select/association/unlink_ context of this reference.
77
+ */
78
+ get compactSelectAssociationUnlink(): Reference {
79
+ return this._contextualize(_contexts.COMPACT_SELECT_ASSOCIATION_UNLINK);
80
+ }
81
+
82
+ /**
83
+ * The _compact/select/foreign_key_ context of this reference.
84
+ */
85
+ get compactSelectForeignKey(): Reference {
86
+ return this._contextualize(_contexts.COMPACT_SELECT_FOREIGN_KEY);
87
+ }
88
+
89
+ /**
90
+ * The _compact/select/foreign_key/bulk_ context of this reference.
91
+ */
92
+ get compactSelectBulkForeignKey(): Reference {
93
+ return this._contextualize(_contexts.COMPACT_SELECT_BULK_FOREIGN_KEY);
94
+ }
95
+
96
+ /**
97
+ * The _compact/select/saved_queries_ context of this reference.
98
+ */
99
+ get compactSelectSavedQueries(): Reference {
100
+ return this._contextualize(_contexts.COMPACT_SELECT_SAVED_QUERIES);
101
+ }
102
+
103
+ /**
104
+ * The _compact/select/show_more_ context of this reference.
105
+ */
106
+ get compactSelectShowMore(): Reference {
107
+ return this._contextualize(_contexts.COMPACT_SELECT_SHOW_MORE);
108
+ }
109
+
110
+ /**
111
+ * The _entry_ context of this reference.
112
+ */
113
+ get entry(): Reference {
114
+ return this._contextualize(_contexts.ENTRY);
115
+ }
116
+
117
+ /**
118
+ * The _entry/create_ context of this reference.
119
+ */
120
+ get entryCreate(): Reference {
121
+ return this._contextualize(_contexts.CREATE);
122
+ }
123
+
124
+ /**
125
+ * The _entry/edit_ context of this reference.
126
+ */
127
+ get entryEdit(): Reference {
128
+ return this._contextualize(_contexts.EDIT);
129
+ }
130
+
131
+ /**
132
+ * The _entry/compact_ context of this reference.
133
+ */
134
+ get compactEntry(): Reference {
135
+ return this._contextualize(_contexts.COMPACT_ENTRY);
136
+ }
137
+
138
+ /**
139
+ * get compactBriefInline - The compact brief inline context of the reference
140
+ */
141
+ get compactBriefInline(): Reference {
142
+ return this._contextualize(_contexts.COMPACT_BRIEF_INLINE);
143
+ }
144
+
145
+ /**
146
+ * get Export - export context
147
+ */
148
+ get export(): Reference {
149
+ return this._contextualize(_contexts.EXPORT);
150
+ }
151
+
152
+ /**
153
+ * get exportCompact - export context for compact view
154
+ */
155
+ get exportCompact(): Reference {
156
+ return this._contextualize(_contexts.EXPORT_COMPACT);
157
+ }
158
+
159
+ /**
160
+ * get exportDetailed - export context for detailed view
161
+ */
162
+ get exportDetailed(): Reference {
163
+ return this._contextualize(_contexts.EXPORT_DETAILED);
164
+ }
165
+
166
+ private _contextualize(context: string): Reference {
167
+ const source = this._reference;
168
+
169
+ const newRef = source.copy();
170
+ newRef.setContext(isStringAndNotEmpty(context) ? context : '');
171
+
172
+ // use the base table to get the alternative table of that context.
173
+ // a base table's .baseTable is itself
174
+ const newTable = source.table._baseTable._getAlternativeTable(context);
175
+ const catalog = newTable.schema.catalog;
176
+
177
+ /**
178
+ * cases:
179
+ * 1. same table: do nothing
180
+ * 2. has join
181
+ * 2.1. source is base, newTable is alternative:
182
+ * - If the join is on the alternative shared key, swap the joins.
183
+ * 2.2. otherwise: use join
184
+ * 3. has facets
185
+ * 3.1. source is base, newTable is alternative, go through filters
186
+ * 3.1.1. If first foreign is from base to alternative, remove it.
187
+ * 3.1.2. otherwise add a fk from alternative to base.
188
+ * 3.2. source is alternative, newTable is base, go through filters
189
+ * 3.1.1. If first foreign is from alternative to base, remove it.
190
+ * 3.1.2. otherwise add a fk from base to alternative.
191
+ * 3.3. source is alternative, newTable is alternative, go through filters
192
+ * 3.1.1. If first foreign is to base, change the first foreignkey to be from the newTable to main.
193
+ * 3.1.2. otherwise add fk from newTable to main table, and from main table to source.
194
+ * 4. doesn't have join
195
+ * 4.1. no filter: swap table and update location only
196
+ * 4.2. has filter
197
+ * 4.2.1. single entity filter using shared key: swap table and convert filter to mapped columns (TODO alt to alt)
198
+ * 4.2.2. otherwise: use join
199
+ *
200
+ * NOTE:
201
+ * If switched to a new table (could be a base table or alternative table)
202
+ * need to update reference's table, key, displayname, location
203
+ * modifiers are not kept because columns are not guarenteed to exist when we switch to another table
204
+ */
205
+ if (newTable !== source.table) {
206
+ // swap to new table
207
+ newRef.setNewTable(newTable);
208
+
209
+ let newLocationString: string | undefined;
210
+ const newFacetFilters: any[] = [];
211
+
212
+ if (source.location.hasJoin) {
213
+ // returns true if join is on alternative shared key
214
+ const joinOnAlternativeKey = (): boolean => {
215
+ // NOTE in some cases the join must be based on
216
+ // aliases and we don't have the column data (share path logic)
217
+ if (!source.location.lastJoin.hasColumnMapping) return false;
218
+
219
+ const joinCols = source.location.lastJoin.toCols;
220
+ const keyCols = source.table._baseTable._altSharedKey.colset.columns;
221
+
222
+ if (joinCols.length !== keyCols.length) {
223
+ return false;
224
+ }
225
+
226
+ return keyCols.every((keyCol) => {
227
+ return joinCols.some((joinCol: any) => joinCol.name === keyCol.name);
228
+ });
229
+ };
230
+
231
+ // creates the new join
232
+ const generateJoin = (): string => {
233
+ /*
234
+ * let's assume we have T1, T1_alt, and T2
235
+ * last join is from T2 to T1-> T2/(id)=(T1:id)
236
+ * now we want to change this to point to T1_alt, to do this we can
237
+ * T2/(id)=(T1:id)/(id)=(T1_alt:id) but the better way is
238
+ * T2/(id)=(T1_alt:id)
239
+ * so we need to map the T1 key that is used in the join to T1_alt key.
240
+ * we can do this only if we know the mapping between foreignkey and key (which is true in this case).
241
+ */
242
+
243
+ const currJoin = source.location.lastJoin;
244
+ const newRightCols: string[] = [];
245
+ let col: any;
246
+
247
+ for (let i = 0; i < currJoin.toCols.length; i++) {
248
+ // find the column object
249
+ col = source.table.columns.get(currJoin.toCols[i]);
250
+
251
+ // map the column from source table to alternative table
252
+ col = newTable._altForeignKey.mapping.getFromColumn(col);
253
+
254
+ // the first column must have schema and table name
255
+ newRightCols.push(i === 0 ? col.toString() : fixedEncodeURIComponent(col.name));
256
+ }
257
+
258
+ return '(' + currJoin.fromColsStr + ')=(' + newRightCols.join(',') + ')';
259
+ };
260
+
261
+ // 2.1. if _altSharedKey is the same as the join
262
+ if (!source.table._isAlternativeTable() && newTable._isAlternativeTable() && joinOnAlternativeKey()) {
263
+ // change to-columns of the join
264
+ newLocationString = source.location.compactUri;
265
+
266
+ // remove the last join
267
+ newLocationString = newLocationString.substring(0, newLocationString.lastIndexOf('/') + 1);
268
+
269
+ // add the new join
270
+ newLocationString += generateJoin();
271
+ }
272
+ } else if (source.location.facets) {
273
+ //TODO needs refactoring
274
+ const currentFacets = JSON.parse(JSON.stringify(source.location.facets.decoded[_FacetsLogicalOperators.AND]));
275
+
276
+ // facetColumns is applying extra logic for alternative, and it only
277
+ // makes sense in the context of facetColumns list. not here.
278
+ // Therefore we should go based on the facets on the location object, not facetColumns.
279
+ // TODO should be modified to support the alternative syntaxes
280
+ const modifyFacetFilters = (funct: (f: any, fk: any) => any): void => {
281
+ currentFacets.forEach((f: any) => {
282
+ if (!f.source) return;
283
+
284
+ let fk = null;
285
+
286
+ //‌ُ TODO this should not be called here, we should refactor this part later
287
+ if (_sourceColumnHelpers._sourceHasNodes(f.source)) {
288
+ let cons: any;
289
+ let isInbound = false;
290
+
291
+ if ('inbound' in f.source[0]) {
292
+ cons = f.source[0].inbound;
293
+ isInbound = true;
294
+ } else if ('outbound' in f.source[0]) {
295
+ cons = f.source[0].outbound;
296
+ } else {
297
+ return;
298
+ }
299
+
300
+ const fkObj = CatalogSerivce.getConstraintObject(catalog.id, cons[0], cons[1]);
301
+ if (fkObj === undefined || fkObj === null || fkObj.subject !== _constraintTypes.FOREIGN_KEY) {
302
+ return;
303
+ }
304
+
305
+ fk = { obj: fkObj.object, isInbound: isInbound };
306
+ }
307
+
308
+ newFacetFilters.push(funct(f, fk));
309
+ });
310
+ };
311
+
312
+ // TODO FILTER_IN_SOURCE
313
+ // source: main table newTable: alternative
314
+ if (!source.table._isAlternativeTable() && newTable._isAlternativeTable()) {
315
+ modifyFacetFilters((facetFilter: any, firstFk: any) => {
316
+ if (firstFk && firstFk.isInbound && firstFk.obj.table === newTable) {
317
+ facetFilter.source.shift();
318
+ if (facetFilter.source.length === 1) {
319
+ facetFilter.source = facetFilter.source[0];
320
+ }
321
+ } else {
322
+ if (!Array.isArray(facetFilter.source)) {
323
+ facetFilter.source = [facetFilter.source];
324
+ }
325
+ facetFilter.source.unshift({ outbound: newTable._altForeignKey.constraint_names[0] });
326
+ }
327
+ return facetFilter;
328
+ });
329
+ }
330
+ // source: alternative newTable: main table
331
+ else if (source.table._isAlternativeTable() && !newTable._isAlternativeTable()) {
332
+ modifyFacetFilters((facetFilter: any, firstFk: any) => {
333
+ if (firstFk && !firstFk.isInbound && firstFk.obj.key.table === newTable) {
334
+ facetFilter.source.shift();
335
+ if (facetFilter.source.length == 1) {
336
+ facetFilter.source = facetFilter.source[0];
337
+ }
338
+ } else {
339
+ if (!Array.isArray(facetFilter.source)) {
340
+ facetFilter.source = [facetFilter.source];
341
+ }
342
+ facetFilter.source.unshift({ inbound: source.table._altForeignKey.constraint_names[0] });
343
+ }
344
+ return facetFilter;
345
+ });
346
+ }
347
+ // source: alternative newTable: alternative
348
+ else {
349
+ modifyFacetFilters((facetFilter: any, firstFk: any) => {
350
+ if (firstFk && !firstFk.isInbound && firstFk.obj.key.table === newTable._baseTable) {
351
+ facetFilter.source[0] = { outbound: newTable._altForeignKey.constraint_names[0] };
352
+ } else {
353
+ if (!Array.isArray(facetFilter.source)) {
354
+ facetFilter.source = [facetFilter.source];
355
+ }
356
+ facetFilter.source.unshift(
357
+ { outbound: newTable._altForeignKey.constraint_names[0] },
358
+ { inbound: source.table._altForeignKey.constraint_names[0] },
359
+ );
360
+ }
361
+ return facetFilter;
362
+ });
363
+ }
364
+
365
+ newLocationString =
366
+ source.location.service +
367
+ '/catalog/' +
368
+ catalog.id +
369
+ '/' +
370
+ source.location.api +
371
+ '/' +
372
+ fixedEncodeURIComponent(newTable.schema.name) +
373
+ ':' +
374
+ fixedEncodeURIComponent(newTable.name);
375
+ } else {
376
+ if (source.location.filter === undefined) {
377
+ // 4.1 no filter
378
+ newLocationString =
379
+ source.location.service +
380
+ '/catalog/' +
381
+ catalog.id +
382
+ '/' +
383
+ source.location.api +
384
+ '/' +
385
+ fixedEncodeURIComponent(newTable.schema.name) +
386
+ ':' +
387
+ fixedEncodeURIComponent(newTable.name);
388
+ } else {
389
+ // 4.2.1 single entity key filter (without any join), swap table and switch to mapping key
390
+ // filter is single entity if it is binary filters using the shared key of the alternative tables
391
+ // or a conjunction of binary predicate that is a key of the alternative tables
392
+
393
+ // use base table's alt shared key
394
+ const sharedKey = source.table._baseTable._altSharedKey;
395
+ const filter = source.location.filter;
396
+ let filterString: string;
397
+
398
+ // binary filters using shared key
399
+ if (filter.type === FILTER_TYPES.BINARYPREDICATE && filter.operator === '=' && sharedKey.colset.length() === 1) {
400
+ // filter using shared key
401
+ if (
402
+ (source.table._isAlternativeTable() && filter.column === source.table._altForeignKey.colset.columns[0].name) ||
403
+ (!source.table._isAlternativeTable() && filter.column === sharedKey.colset.columns[0].name)
404
+ ) {
405
+ if (newTable._isAlternativeTable()) {
406
+ // to alternative table
407
+ filterString = fixedEncodeURIComponent(newTable._altForeignKey.colset.columns[0].name) + '=' + filter.value;
408
+ } else {
409
+ // to base table
410
+ filterString = fixedEncodeURIComponent(sharedKey.colset.columns[0].name) + '=' + filter.value;
411
+ }
412
+
413
+ newLocationString =
414
+ source.location.service +
415
+ '/catalog/' +
416
+ catalog.id +
417
+ '/' +
418
+ source.location.api +
419
+ '/' +
420
+ fixedEncodeURIComponent(newTable.schema.name) +
421
+ ':' +
422
+ fixedEncodeURIComponent(newTable.name) +
423
+ '/' +
424
+ filterString;
425
+ }
426
+ } else if (filter.type === FILTER_TYPES.CONJUNCTION && filter.filters.length === sharedKey.colset.length()) {
427
+ // check that filter is shared key
428
+ let keyColNames: string[];
429
+ if (source.table._isAlternativeTable()) {
430
+ keyColNames = source.table._altForeignKey.colset.columns.map((column: any) => {
431
+ return column.name;
432
+ });
433
+ } else {
434
+ keyColNames = sharedKey.colset.columns.map((column: any) => {
435
+ return column.name;
436
+ });
437
+ }
438
+
439
+ const filterColNames = filter.filters.map((f: any) => {
440
+ return f.column;
441
+ });
442
+
443
+ // all shared key columns must be used in the filters
444
+ if (
445
+ keyColNames.every((keyColName: string) => {
446
+ return filterColNames.indexOf(keyColName) !== -1;
447
+ })
448
+ ) {
449
+ // every filter is binary predicate of "="
450
+ if (
451
+ filter.filters.every((f: any) => {
452
+ return f.type === FILTER_TYPES.BINARYPREDICATE && f.operator === '=';
453
+ })
454
+ ) {
455
+ // find column mapping from source to newRef
456
+ const mapping: { [key: string]: string } = {};
457
+ let newCol: any;
458
+ if (!source.table._isAlternativeTable() && newTable._isAlternativeTable()) {
459
+ // base to alternative
460
+ sharedKey.colset.columns.forEach((column) => {
461
+ newCol = newTable._altForeignKey.mapping.getFromColumn(column);
462
+ mapping[column.name] = newCol.name;
463
+ });
464
+ } else if (source.table._isAlternativeTable() && !newTable._isAlternativeTable()) {
465
+ // alternative to base
466
+ source.table._altForeignKey.colset.columns.forEach((column) => {
467
+ newCol = source.table._altForeignKey.mapping.get(column);
468
+ mapping[column.name] = newCol.name;
469
+ });
470
+ } else {
471
+ // alternative to alternative
472
+ source.table._altForeignKey.colset.columns.forEach((column) => {
473
+ const baseCol = source.table._altForeignKey.mapping.get(column); // alt 1 col -> base col
474
+ newCol = newTable._altForeignKey.mapping.getFromColumn(baseCol); // base col -> alt 2
475
+ mapping[column.name] = newCol.name;
476
+ });
477
+ }
478
+
479
+ filterString = '';
480
+
481
+ for (let j = 0; j < filter.filters.length; j++) {
482
+ const f = filter.filters[j];
483
+ // map column
484
+ filterString += (j === 0 ? '' : '&') + fixedEncodeURIComponent(mapping[f.column]) + '=' + fixedEncodeURIComponent(f.value);
485
+ }
486
+
487
+ newLocationString =
488
+ source.location.service +
489
+ '/catalog/' +
490
+ catalog.id +
491
+ '/' +
492
+ source.location.api +
493
+ '/' +
494
+ fixedEncodeURIComponent(newTable.schema.name) +
495
+ ':' +
496
+ fixedEncodeURIComponent(newTable.name) +
497
+ '/' +
498
+ filterString;
499
+ }
500
+ }
501
+ }
502
+ }
503
+ }
504
+
505
+ if (!newLocationString) {
506
+ // all other cases (2.2., 3.2.2), use join
507
+ let join: string;
508
+ if (source.table._isAlternativeTable() && newTable._isAlternativeTable()) {
509
+ join = source.table._altForeignKey.toString(true) + '/' + newTable._altForeignKey.toString();
510
+ } else if (!source.table._isAlternativeTable()) {
511
+ // base to alternative
512
+ join = newTable._altForeignKey.toString();
513
+ } else {
514
+ // alternative to base
515
+ join = source.table._altForeignKey.toString(true);
516
+ }
517
+ newLocationString = source.location.compactUri + '/' + join;
518
+ }
519
+
520
+ //add the query parameters
521
+ if (source.location.queryParamsString) {
522
+ newLocationString += '?' + source.location.queryParamsString;
523
+ }
524
+
525
+ newRef.setLocation(parse(newLocationString, catalog));
526
+
527
+ // change the face filters
528
+ if (newFacetFilters.length > 0) {
529
+ newRef.location.facets = { and: newFacetFilters };
530
+ }
531
+ }
532
+
533
+ return newRef;
534
+ }
535
+ }