@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,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
|
+
}
|