electrodb 3.6.2 → 3.7.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/index.d.ts +7 -6
- package/package.json +1 -1
- package/src/clauses.js +90 -59
- package/src/entity.js +445 -154
- package/src/errors.js +24 -12
- package/src/operations.js +3 -3
- package/src/service.js +28 -24
- package/src/types.js +3 -0
- package/src/validations.js +4 -7
- package/dist/index.test-d.js +0 -4043
- package/dist/index.test-d.js.map +0 -1
package/src/entity.js
CHANGED
|
@@ -33,7 +33,7 @@ const {
|
|
|
33
33
|
IndexProjectionOptions,
|
|
34
34
|
} = require("./types");
|
|
35
35
|
const { FilterFactory } = require("./filters");
|
|
36
|
-
const { FilterOperations, formatExpressionName } = require("./operations");
|
|
36
|
+
const { FilterOperations, ExpressionState, formatExpressionName } = require("./operations");
|
|
37
37
|
const { WhereFactory } = require("./where");
|
|
38
38
|
const { clauses, ChainState } = require("./clauses");
|
|
39
39
|
const { EventManager } = require("./events");
|
|
@@ -312,6 +312,8 @@ class Entity {
|
|
|
312
312
|
const chain = this._makeChain(index, clauses, clauses.index, chainOptions);
|
|
313
313
|
if (options.indexType === IndexTypes.clustered) {
|
|
314
314
|
return chain.clusteredCollection(collection, facets);
|
|
315
|
+
} else if (options.indexType === IndexTypes.composite) {
|
|
316
|
+
return chain.compositeCollection(collection, facets);
|
|
315
317
|
} else {
|
|
316
318
|
return chain.collection(collection, facets);
|
|
317
319
|
}
|
|
@@ -432,21 +434,19 @@ class Entity {
|
|
|
432
434
|
}
|
|
433
435
|
|
|
434
436
|
async transactWrite(parameters, config) {
|
|
435
|
-
|
|
437
|
+
return this._exec(
|
|
436
438
|
MethodTypes.transactWrite,
|
|
437
439
|
parameters,
|
|
438
440
|
config,
|
|
439
441
|
);
|
|
440
|
-
return response;
|
|
441
442
|
}
|
|
442
443
|
|
|
443
444
|
async transactGet(parameters, config) {
|
|
444
|
-
|
|
445
|
+
return this._exec(
|
|
445
446
|
MethodTypes.transactGet,
|
|
446
447
|
parameters,
|
|
447
448
|
config,
|
|
448
449
|
);
|
|
449
|
-
return response;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
async go(method, parameters = {}, config = {}) {
|
|
@@ -1035,12 +1035,13 @@ class Entity {
|
|
|
1035
1035
|
let keys = {};
|
|
1036
1036
|
const secondaryIndexStrictMode =
|
|
1037
1037
|
options.strict === "all" || options.strict === "pk" ? "pk" : "none";
|
|
1038
|
-
for (const
|
|
1038
|
+
for (const index of Object.values(this.model.indexes)) {
|
|
1039
|
+
const indexName = index.index;
|
|
1039
1040
|
const indexKeys = this._fromCompositeToKeysByIndex(
|
|
1040
|
-
{ indexName
|
|
1041
|
+
{ indexName, provided },
|
|
1041
1042
|
{
|
|
1042
1043
|
strict:
|
|
1043
|
-
|
|
1044
|
+
indexName === TableIndex ? options.strict : secondaryIndexStrictMode,
|
|
1044
1045
|
},
|
|
1045
1046
|
);
|
|
1046
1047
|
if (indexKeys) {
|
|
@@ -1224,21 +1225,28 @@ class Entity {
|
|
|
1224
1225
|
) {
|
|
1225
1226
|
let allKeys = {};
|
|
1226
1227
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1228
|
+
if (this._getIndexType(indexName) === IndexTypes.composite) {
|
|
1229
|
+
const item = this.model.schema.translateFromFields(provided);
|
|
1230
|
+
allKeys = {
|
|
1231
|
+
...this._findFacets(item, this.model.facets.byIndex[indexName].pk),
|
|
1232
|
+
...this._findFacets(item, this.model.facets.byIndex[indexName].sk),
|
|
1233
|
+
}
|
|
1234
|
+
} else {
|
|
1235
|
+
const indexKeys = this._deconstructIndex({
|
|
1236
|
+
index: indexName,
|
|
1237
|
+
keys: provided,
|
|
1238
|
+
});
|
|
1239
|
+
if (!indexKeys) {
|
|
1240
|
+
throw new e.ElectroError(
|
|
1241
|
+
e.ErrorCodes.InvalidConversionKeysProvided,
|
|
1242
|
+
`Provided keys did not include valid properties for the index "${indexName}"`,
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
allKeys = {
|
|
1246
|
+
...indexKeys,
|
|
1247
|
+
};
|
|
1236
1248
|
}
|
|
1237
1249
|
|
|
1238
|
-
allKeys = {
|
|
1239
|
-
...indexKeys,
|
|
1240
|
-
};
|
|
1241
|
-
|
|
1242
1250
|
let tableKeys;
|
|
1243
1251
|
if (indexName !== TableIndex) {
|
|
1244
1252
|
tableKeys = this._deconstructIndex({ index: TableIndex, keys: provided });
|
|
@@ -1283,10 +1291,17 @@ class Entity {
|
|
|
1283
1291
|
);
|
|
1284
1292
|
}
|
|
1285
1293
|
|
|
1294
|
+
_getIndexType(indexName) {
|
|
1295
|
+
return this.model.facets.byIndex[indexName].type;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1286
1298
|
_trimKeysToIndex({ indexName = TableIndex, provided }) {
|
|
1287
1299
|
if (!provided) {
|
|
1288
1300
|
return null;
|
|
1289
1301
|
}
|
|
1302
|
+
if (this.model.facets.byIndex[indexName].type === IndexTypes.composite) {
|
|
1303
|
+
return provided;
|
|
1304
|
+
}
|
|
1290
1305
|
|
|
1291
1306
|
const pkName = this.model.translations.keys[indexName].pk;
|
|
1292
1307
|
const skName = this.model.translations.keys[indexName].sk;
|
|
@@ -1315,13 +1330,17 @@ class Entity {
|
|
|
1315
1330
|
);
|
|
1316
1331
|
}
|
|
1317
1332
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1333
|
+
if (this._getIndexType(indexName) === IndexTypes.composite) {
|
|
1334
|
+
const item = this.model.schema.translateFromFields(provided);
|
|
1335
|
+
return this.model.facets.byIndex[indexName].pk.every((attr) => item[attr] !== undefined);
|
|
1336
|
+
} else {
|
|
1337
|
+
const pkName = this.model.translations.keys[indexName].pk;
|
|
1338
|
+
const skName = this.model.translations.keys[indexName].sk;
|
|
1339
|
+
return (
|
|
1340
|
+
provided[pkName] !== undefined &&
|
|
1341
|
+
(!skName || provided[skName] !== undefined)
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1325
1344
|
}
|
|
1326
1345
|
|
|
1327
1346
|
_formatReturnPager(config, lastEvaluatedKey) {
|
|
@@ -1585,12 +1604,19 @@ class Entity {
|
|
|
1585
1604
|
|
|
1586
1605
|
_constructPagerIndex(index = TableIndex, item, options = {}) {
|
|
1587
1606
|
let pkAttributes = options.relaxedPk
|
|
1588
|
-
? item
|
|
1607
|
+
? this._findFacets(item, this.model.facets.byIndex[index].pk)
|
|
1589
1608
|
: this._expectFacets(item, this.model.facets.byIndex[index].pk);
|
|
1590
1609
|
let skAttributes = options.relaxedSk
|
|
1591
|
-
? item
|
|
1610
|
+
? this._findFacets(item, this.model.facets.byIndex[index].sk)
|
|
1592
1611
|
: this._expectFacets(item, this.model.facets.byIndex[index].sk);
|
|
1593
1612
|
|
|
1613
|
+
if (this._getIndexType(index) === IndexTypes.composite) {
|
|
1614
|
+
return this.model.schema.translateToFields({
|
|
1615
|
+
...pkAttributes,
|
|
1616
|
+
...skAttributes,
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1594
1620
|
let keys = this._makeIndexKeys({
|
|
1595
1621
|
index,
|
|
1596
1622
|
pkAttributes,
|
|
@@ -2257,20 +2283,20 @@ class Entity {
|
|
|
2257
2283
|
return key;
|
|
2258
2284
|
}
|
|
2259
2285
|
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
}
|
|
2286
|
+
applyIdentifierExpressionState(expressionState, alias) {
|
|
2287
|
+
const name = this.getName();
|
|
2288
|
+
const version = this.getVersion();
|
|
2289
|
+
const nameRef = expressionState.setName({}, this.identifiers.entity, this.identifiers.entity);
|
|
2290
|
+
const versionRef = expressionState.setName({}, this.identifiers.version, this.identifiers.version);
|
|
2291
|
+
const nameVal = expressionState.setValue(
|
|
2292
|
+
`${this.identifiers.entity}_${alias || name}`,
|
|
2293
|
+
name,
|
|
2294
|
+
);
|
|
2295
|
+
const versionVal = expressionState.setValue(
|
|
2296
|
+
`${this.identifiers.version}_${alias || name}`,
|
|
2297
|
+
version,
|
|
2298
|
+
);
|
|
2299
|
+
return `(${nameRef.expression} = ${nameVal} AND ${versionRef.expression} = ${versionVal})`;
|
|
2274
2300
|
}
|
|
2275
2301
|
|
|
2276
2302
|
/* istanbul ignore next */
|
|
@@ -2682,7 +2708,7 @@ class Entity {
|
|
|
2682
2708
|
}
|
|
2683
2709
|
expressions.UpdateExpression = `${operation.toUpperCase()} ${expressions.UpdateExpression.join(
|
|
2684
2710
|
", ",
|
|
2685
|
-
)}
|
|
2711
|
+
)}`.trim();
|
|
2686
2712
|
return expressions;
|
|
2687
2713
|
}
|
|
2688
2714
|
|
|
@@ -2709,61 +2735,236 @@ class Entity {
|
|
|
2709
2735
|
}
|
|
2710
2736
|
}
|
|
2711
2737
|
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2738
|
+
|
|
2739
|
+
_compositeQueryParams(state = {}, options = {}) {
|
|
2740
|
+
// todo: review "_consolidateQueryFacets"
|
|
2741
|
+
const consolidated = this._consolidateQueryFacets(
|
|
2742
|
+
state.query.keys.sk,
|
|
2743
|
+
) || [];
|
|
2744
|
+
|
|
2745
|
+
const pkAttributes = state.query.keys.pk;
|
|
2746
|
+
const skAttributes = consolidated[0] || {};
|
|
2747
|
+
|
|
2748
|
+
// provided has length, isArray?
|
|
2749
|
+
const provided = state.query.keys.provided;
|
|
2750
|
+
const all = state.query.facets.all || [];
|
|
2751
|
+
const queryType = state.query.type;
|
|
2752
|
+
const expressionState = new ExpressionState({ prefix: "k_" });
|
|
2753
|
+
|
|
2754
|
+
const expressions = [];
|
|
2755
|
+
if (queryType === QueryTypes.between) {
|
|
2756
|
+
for (const [name, value] of Object.entries(pkAttributes)) {
|
|
2757
|
+
const field = this.model.schema.getFieldName(name);
|
|
2758
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2759
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2760
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2761
|
+
}
|
|
2762
|
+
let is = {}
|
|
2763
|
+
let start = {}
|
|
2764
|
+
let end = {};
|
|
2765
|
+
(state.query.keys.sk ?? []).forEach(({type, facets}) => {
|
|
2766
|
+
if (type === QueryTypes.is || type === QueryTypes.composite_collection) {
|
|
2767
|
+
is = facets;
|
|
2768
|
+
} else if (type === QueryTypes.between) {
|
|
2769
|
+
start = facets;
|
|
2770
|
+
} else if (type === QueryTypes.and) {
|
|
2771
|
+
end = facets;
|
|
2772
|
+
} else {
|
|
2773
|
+
// todo: improve error handling
|
|
2774
|
+
throw new Error('Internal error: Invalid sort key type in composite between query');
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
|
|
2778
|
+
let lastFound;
|
|
2779
|
+
const skNames = state.query.facets.sk || [];
|
|
2780
|
+
for (const name of skNames) {
|
|
2781
|
+
if (is[name] !== undefined) {
|
|
2782
|
+
lastFound = name;
|
|
2783
|
+
} else if (start[name] !== undefined && end[name] !== undefined) {
|
|
2784
|
+
lastFound = name;
|
|
2785
|
+
} else if (start[name] !== undefined || end[name] !== undefined) {
|
|
2786
|
+
throw new e.ElectroError(
|
|
2787
|
+
e.ErrorCodes.InvalidQueryParameters,
|
|
2788
|
+
`Invalid attribute combination provided to between query. Between queries on composite indexes must have the same attribute for start and end values until the last sort key attribute. The provided attribute ${name} is missing ${start[name] !== undefined ? 'an end' : 'a start'} value. This is a DynamoDB constraint.`
|
|
2789
|
+
);
|
|
2790
|
+
} else {
|
|
2791
|
+
break;
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
for (let i = 0; i < skNames.length; i++) {
|
|
2796
|
+
const name = skNames[i];
|
|
2797
|
+
if (lastFound === name) {
|
|
2798
|
+
const startValue = start[name];
|
|
2799
|
+
const endValue = end[name];
|
|
2800
|
+
const field = this.model.schema.getFieldName(name);
|
|
2801
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2802
|
+
const startValueRef = expressionState.setValue(name, startValue);
|
|
2803
|
+
const endValueRef = expressionState.setValue(name, endValue);
|
|
2804
|
+
expressions.push(`${nameRef.expression} BETWEEN ${startValueRef} AND ${endValueRef}`);
|
|
2805
|
+
} else if (is[name] !== undefined) {
|
|
2806
|
+
const value = is[name];
|
|
2807
|
+
const field = this.model.schema.getFieldName(name);
|
|
2808
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2809
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2810
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2811
|
+
} else if (start[name] !== undefined && end[name] !== undefined) {
|
|
2812
|
+
if (start[name] !== end[name]) {
|
|
2813
|
+
throw new e.ElectroError(
|
|
2814
|
+
e.ErrorCodes.InvalidQueryParameters,
|
|
2815
|
+
`Invalid attribute combination provided to between query. Between queries on composite indexes must have the same attribute for start and end values until the last sort key attribute. The provided attribute ${name} has different start and end values. This is a DynamoDB constraint.`
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
const value = start[name];
|
|
2819
|
+
const field = this.model.schema.getFieldName(name);
|
|
2820
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2821
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2822
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
} else {
|
|
2826
|
+
const attrs = [];
|
|
2827
|
+
for (const { type, name } of all) {
|
|
2828
|
+
const value = type === "pk" ? pkAttributes[name] : skAttributes[name];
|
|
2829
|
+
if (value === undefined) {
|
|
2830
|
+
break;
|
|
2831
|
+
}
|
|
2832
|
+
attrs.push({type, name, value});
|
|
2833
|
+
}
|
|
2834
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
2835
|
+
const { type, name, value } = attrs[i];
|
|
2836
|
+
const field = this.model.schema.getFieldName(name);
|
|
2837
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2838
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2839
|
+
const shouldApplyEq = !(type === "sk" && i === attrs.length - 1)
|
|
2840
|
+
if (shouldApplyEq) {
|
|
2841
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2842
|
+
continue;
|
|
2843
|
+
}
|
|
2844
|
+
switch (queryType) {
|
|
2845
|
+
case QueryTypes.is:
|
|
2846
|
+
case QueryTypes.eq:
|
|
2847
|
+
case QueryTypes.collection:
|
|
2848
|
+
case QueryTypes.composite_collection:
|
|
2849
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2850
|
+
break;
|
|
2851
|
+
case QueryTypes.begins:
|
|
2852
|
+
expressions.push(`begins_with(${nameRef.expression}, ${valueRef})`);
|
|
2853
|
+
break;
|
|
2854
|
+
case QueryTypes.gt:
|
|
2855
|
+
expressions.push(`${nameRef.expression} > ${valueRef}`);
|
|
2856
|
+
break;
|
|
2857
|
+
case QueryTypes.gte:
|
|
2858
|
+
expressions.push(`${nameRef.expression} >= ${valueRef}`);
|
|
2859
|
+
break;
|
|
2860
|
+
case QueryTypes.lt:
|
|
2861
|
+
expressions.push(`${nameRef.expression} < ${valueRef}`);
|
|
2862
|
+
break;
|
|
2863
|
+
case QueryTypes.lte:
|
|
2864
|
+
expressions.push(`${nameRef.expression} <= ${valueRef}`);
|
|
2865
|
+
break;
|
|
2866
|
+
case QueryTypes.between: {
|
|
2867
|
+
const second = consolidated[consolidated.length - 1];
|
|
2868
|
+
const value2 = second[name];
|
|
2869
|
+
const valueRef2 = expressionState.setValue(name, value2);
|
|
2870
|
+
expressions.push(`${nameRef.expression} BETWEEN ${valueRef} AND ${valueRef2}`);
|
|
2871
|
+
break;
|
|
2872
|
+
}
|
|
2873
|
+
// todo: clean up here
|
|
2874
|
+
case QueryTypes.clustered_collection:
|
|
2875
|
+
default:
|
|
2876
|
+
// todo: improve error handling
|
|
2877
|
+
throw new Error('Not supported')
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
const filter = state.query.filter[ExpressionTypes.FilterExpression];
|
|
2883
|
+
const customExpressions = {
|
|
2884
|
+
names: (state.query.options.expressions && state.query.options.expressions.names) || {},
|
|
2885
|
+
values: (state.query.options.expressions && state.query.options.expressions.values) || {},
|
|
2886
|
+
expression: (state.query.options.expressions && state.query.options.expressions.expression) || "",
|
|
2887
|
+
};
|
|
2888
|
+
|
|
2889
|
+
// identifiers are added via custom expressions on collection queries inside `clauses/handleNonIsolatedCollection`
|
|
2890
|
+
// Don't duplicate filters if they are provided.
|
|
2891
|
+
const identifierExpression = !options.ignoreOwnership && !customExpressions.expression ? this.applyIdentifierExpressionState(expressionState) : '';
|
|
2892
|
+
|
|
2893
|
+
const params = {
|
|
2894
|
+
IndexName: state.query.index,
|
|
2895
|
+
KeyConditionExpression: expressions.join(' AND '),
|
|
2896
|
+
TableName: this.getTableName(),
|
|
2897
|
+
ExpressionAttributeNames: this._mergeExpressionsAttributes(
|
|
2898
|
+
filter.getNames(),
|
|
2899
|
+
expressionState.getNames(),
|
|
2900
|
+
customExpressions.names,
|
|
2901
|
+
),
|
|
2902
|
+
ExpressionAttributeValues: this._mergeExpressionsAttributes(
|
|
2903
|
+
filter.getValues(),
|
|
2904
|
+
expressionState.getValues(),
|
|
2905
|
+
customExpressions.values,
|
|
2906
|
+
),
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
let filerExpressions = [customExpressions.expression || "", filter.build(), identifierExpression]
|
|
2910
|
+
.map(s => s.trim())
|
|
2911
|
+
.filter(Boolean)
|
|
2912
|
+
.join(" AND ");
|
|
2913
|
+
|
|
2914
|
+
if (filerExpressions.length) {
|
|
2915
|
+
params.FilterExpression = filerExpressions;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
return params;
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
_makeQueryParams(state = {}, options = {}, indexKeys) {
|
|
2716
2922
|
switch (state.query.type) {
|
|
2717
2923
|
case QueryTypes.is:
|
|
2718
|
-
|
|
2924
|
+
return this._makeIsQueryParams(
|
|
2719
2925
|
state.query,
|
|
2720
2926
|
state.query.index,
|
|
2721
2927
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2722
2928
|
indexKeys.pk,
|
|
2723
2929
|
...indexKeys.sk,
|
|
2724
2930
|
);
|
|
2725
|
-
break;
|
|
2726
2931
|
case QueryTypes.begins:
|
|
2727
|
-
|
|
2932
|
+
return this._makeBeginsWithQueryParams(
|
|
2728
2933
|
state.query.options,
|
|
2729
2934
|
state.query.index,
|
|
2730
2935
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2731
2936
|
indexKeys.pk,
|
|
2732
2937
|
...indexKeys.sk,
|
|
2733
2938
|
);
|
|
2734
|
-
break;
|
|
2735
2939
|
case QueryTypes.collection:
|
|
2736
|
-
|
|
2940
|
+
return this._makeBeginsWithQueryParams(
|
|
2737
2941
|
state.query.options,
|
|
2738
2942
|
state.query.index,
|
|
2739
2943
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2740
2944
|
indexKeys.pk,
|
|
2741
2945
|
this._getCollectionSk(state.query.collection),
|
|
2742
2946
|
);
|
|
2743
|
-
break;
|
|
2744
2947
|
case QueryTypes.clustered_collection:
|
|
2745
|
-
|
|
2948
|
+
return this._makeBeginsWithQueryParams(
|
|
2746
2949
|
state.query.options,
|
|
2747
2950
|
state.query.index,
|
|
2748
2951
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2749
2952
|
indexKeys.pk,
|
|
2750
2953
|
...indexKeys.sk,
|
|
2751
2954
|
);
|
|
2752
|
-
break;
|
|
2753
2955
|
case QueryTypes.between:
|
|
2754
|
-
|
|
2956
|
+
return this._makeBetweenQueryParams(
|
|
2755
2957
|
state.query.options,
|
|
2756
2958
|
state.query.index,
|
|
2757
2959
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2758
2960
|
indexKeys.pk,
|
|
2759
2961
|
...indexKeys.sk,
|
|
2760
2962
|
);
|
|
2761
|
-
break;
|
|
2762
2963
|
case QueryTypes.gte:
|
|
2763
2964
|
case QueryTypes.gt:
|
|
2764
2965
|
case QueryTypes.lte:
|
|
2765
2966
|
case QueryTypes.lt:
|
|
2766
|
-
|
|
2967
|
+
return this._makeComparisonQueryParams(
|
|
2767
2968
|
state.query.index,
|
|
2768
2969
|
state.query.type,
|
|
2769
2970
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
@@ -2771,10 +2972,20 @@ class Entity {
|
|
|
2771
2972
|
options,
|
|
2772
2973
|
state.query.options,
|
|
2773
2974
|
);
|
|
2774
|
-
break;
|
|
2775
2975
|
default:
|
|
2776
2976
|
throw new Error(`Invalid query type: ${state.query.type}`);
|
|
2777
2977
|
}
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
/* istanbul ignore next */
|
|
2981
|
+
_queryParams(state = {}, options = {}) {
|
|
2982
|
+
const indexKeys = this._makeQueryKeys(state, options);
|
|
2983
|
+
let parameters;
|
|
2984
|
+
if (state.query.options.indexType !== IndexTypes.composite) {
|
|
2985
|
+
parameters = this._makeQueryParams(state, options, indexKeys);
|
|
2986
|
+
} else {
|
|
2987
|
+
parameters = this._compositeQueryParams(state, options);
|
|
2988
|
+
}
|
|
2778
2989
|
|
|
2779
2990
|
const appliedParameters = this._applyParameterOptions({
|
|
2780
2991
|
params: parameters,
|
|
@@ -3064,8 +3275,12 @@ class Entity {
|
|
|
3064
3275
|
_makeKeysFromAttributes(indexes, attributes, conditions) {
|
|
3065
3276
|
let indexKeys = {};
|
|
3066
3277
|
for (let [index, keyTypes] of Object.entries(indexes)) {
|
|
3278
|
+
if (this.model.lookup.compositeIndexes.has(index)) {
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3067
3282
|
const shouldMakeKeys =
|
|
3068
|
-
!this._indexConditionIsDefined(index) || conditions[index];
|
|
3283
|
+
(!this._indexConditionIsDefined(index) || conditions[index]);
|
|
3069
3284
|
if (!shouldMakeKeys && index !== TableIndex) {
|
|
3070
3285
|
continue;
|
|
3071
3286
|
}
|
|
@@ -3285,6 +3500,11 @@ class Entity {
|
|
|
3285
3500
|
if (attributes[attribute] !== undefined) {
|
|
3286
3501
|
facets[attribute] = attributes[attribute];
|
|
3287
3502
|
indexes.forEach((definition) => {
|
|
3503
|
+
// composite indexes do not have keys
|
|
3504
|
+
if (definition.type === IndexTypes.composite) {
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3288
3508
|
const { index, type } = definition;
|
|
3289
3509
|
impactedIndexes[index] = impactedIndexes[index] || {};
|
|
3290
3510
|
impactedIndexes[index][type] = impactedIndexes[index][type] || [];
|
|
@@ -3303,9 +3523,12 @@ class Entity {
|
|
|
3303
3523
|
|
|
3304
3524
|
// this function is used to determine key impact for update `set`, update `delete`, and `put`. This block is currently only used by update `set`
|
|
3305
3525
|
if (utilizeIncludedOnlyIndexes) {
|
|
3306
|
-
for (const [index, { pk, sk }] of Object.entries(
|
|
3526
|
+
for (const [index, { pk, sk, type }] of Object.entries(
|
|
3307
3527
|
this.model.facets.byIndex,
|
|
3308
3528
|
)) {
|
|
3529
|
+
if (type === IndexTypes.composite) {
|
|
3530
|
+
continue;
|
|
3531
|
+
}
|
|
3309
3532
|
// The main table index is handled somewhere else (messy I know), and we only want to do this processing if an
|
|
3310
3533
|
// index condition is defined for backwards compatibility. Backwards compatibility is not required for this
|
|
3311
3534
|
// change, but I have paranoid concerns of breaking changes around sparse indexes.
|
|
@@ -3367,10 +3590,12 @@ class Entity {
|
|
|
3367
3590
|
}
|
|
3368
3591
|
}
|
|
3369
3592
|
|
|
3370
|
-
let indexesWithMissingComposites =
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3593
|
+
let indexesWithMissingComposites = [];
|
|
3594
|
+
for (const [index, definition] of Object.entries(this.model.facets.byIndex)) {
|
|
3595
|
+
const { pk, sk, type } = definition;
|
|
3596
|
+
if (type === IndexTypes.composite) {
|
|
3597
|
+
continue;
|
|
3598
|
+
}
|
|
3374
3599
|
let impacted = impactedIndexes[index];
|
|
3375
3600
|
let impact = {
|
|
3376
3601
|
index,
|
|
@@ -3408,8 +3633,8 @@ class Entity {
|
|
|
3408
3633
|
}
|
|
3409
3634
|
}
|
|
3410
3635
|
|
|
3411
|
-
|
|
3412
|
-
}
|
|
3636
|
+
indexesWithMissingComposites.push(impact);
|
|
3637
|
+
}
|
|
3413
3638
|
|
|
3414
3639
|
let incomplete = [];
|
|
3415
3640
|
for (const { index, missing, definition } of indexesWithMissingComposites) {
|
|
@@ -3536,7 +3761,11 @@ class Entity {
|
|
|
3536
3761
|
}
|
|
3537
3762
|
}
|
|
3538
3763
|
|
|
3539
|
-
|
|
3764
|
+
_findFacets(obj, properties) {
|
|
3765
|
+
return Object.fromEntries(this._findProperties(obj, properties));
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
_findProperties(obj, properties = []) {
|
|
3540
3769
|
return properties.map((name) => [name, obj[name]]);
|
|
3541
3770
|
}
|
|
3542
3771
|
|
|
@@ -3707,8 +3936,8 @@ class Entity {
|
|
|
3707
3936
|
e.ErrorCodes.IncompatibleKeyCasing,
|
|
3708
3937
|
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
3709
3938
|
tableIndex.index,
|
|
3710
|
-
)}' is defined with the casing ${keys.pk.casing}, but the
|
|
3711
|
-
previouslyDefinedPk.
|
|
3939
|
+
)}' is defined with the casing ${keys.pk.casing}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
3940
|
+
previouslyDefinedPk.definition.accessPattern,
|
|
3712
3941
|
)}' defines the same index field with the ${previouslyDefinedPk.definition.casing === DefaultKeyCasing ? '(default)' : ''} casing ${previouslyDefinedPk.definition.casing}. Key fields must have the same casing definitions across all indexes they are involved with.`,
|
|
3713
3942
|
);
|
|
3714
3943
|
}
|
|
@@ -3723,8 +3952,8 @@ class Entity {
|
|
|
3723
3952
|
e.ErrorCodes.IncompatibleKeyCasing,
|
|
3724
3953
|
`Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
3725
3954
|
tableIndex.index,
|
|
3726
|
-
)}' is defined with the casing ${keys.sk.casing}, but the
|
|
3727
|
-
previouslyDefinedSk.
|
|
3955
|
+
)}' is defined with the casing ${keys.sk.casing}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
3956
|
+
previouslyDefinedSk.definition.accessPattern,
|
|
3728
3957
|
)}' defines the same index field with the ${previouslyDefinedSk.definition.casing === DefaultKeyCasing ? '(default)' : ''} casing ${previouslyDefinedSk.definition.casing}. Key fields must have the same casing definitions across all indexes they are involved with.`,
|
|
3729
3958
|
);
|
|
3730
3959
|
}
|
|
@@ -3832,7 +4061,6 @@ class Entity {
|
|
|
3832
4061
|
this.model.facets.labels[index] &&
|
|
3833
4062
|
Array.isArray(this.model.facets.labels[index].sk);
|
|
3834
4063
|
let labels = hasLabels ? this.model.facets.labels[index].sk : [];
|
|
3835
|
-
// const hasFacets = Object.keys(skFacet).length > 0;
|
|
3836
4064
|
let sortKey = this._makeKey(prefixes.sk, facets.sk, skFacet, labels, {
|
|
3837
4065
|
excludeLabelTail: true,
|
|
3838
4066
|
excludePostfix,
|
|
@@ -4277,6 +4505,7 @@ class Entity {
|
|
|
4277
4505
|
let indexHasSortKeys = {};
|
|
4278
4506
|
let indexHasSubCollections = {};
|
|
4279
4507
|
let clusteredIndexes = new Set();
|
|
4508
|
+
let compositeIndexes = new Set();
|
|
4280
4509
|
let indexAccessPatternTransaction = {
|
|
4281
4510
|
fromAccessPatternToIndex: {},
|
|
4282
4511
|
fromIndexToAccessPattern: {},
|
|
@@ -4320,9 +4549,48 @@ class Entity {
|
|
|
4320
4549
|
let conditionDefined = v.isFunction(index.condition);
|
|
4321
4550
|
let indexCondition = index.condition || (() => true);
|
|
4322
4551
|
|
|
4323
|
-
if (indexType ===
|
|
4552
|
+
if (indexType === IndexTypes.clustered) {
|
|
4553
|
+
// todo: make contents consistent with "compositeIndexes" below
|
|
4554
|
+
// this is not consistent with "compositeIndexes" (which uses the index name), this should be fixed in the future.
|
|
4324
4555
|
clusteredIndexes.add(accessPattern);
|
|
4556
|
+
} else if (indexType === IndexTypes.composite) {
|
|
4557
|
+
if (indexName === TableIndex) {
|
|
4558
|
+
throw new e.ElectroError(
|
|
4559
|
+
e.ErrorCodes.InvalidIndexDefinition,
|
|
4560
|
+
`The Access Pattern "${accessPattern}" cannot be defined as a composite index. AWS DynamoDB does not allow for composite indexes on the main table index.`,
|
|
4561
|
+
);
|
|
4562
|
+
}
|
|
4563
|
+
if (conditionDefined) {
|
|
4564
|
+
throw new e.ElectroError(
|
|
4565
|
+
e.ErrorCodes.InvalidIndexCondition,
|
|
4566
|
+
`The Access Pattern "${accessPattern}" is defined as a "${indexType}" index, but a condition callback is defined. Composite indexes do not support the use of a condition callback.`,
|
|
4567
|
+
);
|
|
4568
|
+
}
|
|
4569
|
+
if (index.scope !== undefined) {
|
|
4570
|
+
throw new e.ElectroError(
|
|
4571
|
+
e.ErrorCodes.InvalidIndexCondition,
|
|
4572
|
+
`The Access Pattern "${accessPattern}" is defined as a "${indexType}" index, but a "scope" value was defined. Composite indexes do not support the use of scope.`,
|
|
4573
|
+
);
|
|
4574
|
+
}
|
|
4575
|
+
if (index.pk.field !== undefined || (index.sk && index.sk.field !== undefined)) {
|
|
4576
|
+
throw new e.ElectroError(
|
|
4577
|
+
e.ErrorCodes.InvalidIndexDefinition,
|
|
4578
|
+
`The Access Pattern "${accessPattern}" is defined as a "${indexType}" index, but the Partition Key or Sort Key is defined with a field property. Composite indexes do not support the use of a field property, their attributes defined in the composite array define the indexes member attributes.`,
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
// this is not consistent with "clusteredIndexes" (which uses the access pattern name), but it is more correct given the naming.
|
|
4582
|
+
compositeIndexes.add(indexName);
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
if (indexType !== IndexTypes.composite) {
|
|
4586
|
+
if (index.pk.field === undefined || (index.sk && index.sk.field === undefined)) {
|
|
4587
|
+
throw new e.ElectroError(
|
|
4588
|
+
e.ErrorCodes.InvalidIndexDefinition,
|
|
4589
|
+
`The Access Pattern "${accessPattern}" is defined as a "${indexType}" index, but the Partition Key or Sort Key is defined without a field property. Unless using composite attributes, indexes must be defined with a field property that maps to the field name on the DynamoDB table KeySchema.`,
|
|
4590
|
+
);
|
|
4591
|
+
}
|
|
4325
4592
|
}
|
|
4593
|
+
|
|
4326
4594
|
if (seenIndexes[indexName] !== undefined) {
|
|
4327
4595
|
if (indexName === TableIndex) {
|
|
4328
4596
|
throw new e.ElectroError(
|
|
@@ -4349,6 +4617,22 @@ class Entity {
|
|
|
4349
4617
|
)}', contains a collection definition without a defined SK. Collections can only be defined on indexes with a defined SK.`,
|
|
4350
4618
|
);
|
|
4351
4619
|
}
|
|
4620
|
+
|
|
4621
|
+
if (indexType !== IndexTypes.composite) {
|
|
4622
|
+
if (hasSk && index.sk.field === undefined) {
|
|
4623
|
+
throw new e.ElectroError(
|
|
4624
|
+
e.ErrorCodes.InvalidIndexCompositeAttributes,
|
|
4625
|
+
`The ${accessPattern} Access pattern is defined as a "${indexType}" index, but a Sort Key is defined without a Range Key field mapping.`,
|
|
4626
|
+
);
|
|
4627
|
+
}
|
|
4628
|
+
if (index.pk.field === undefined) {
|
|
4629
|
+
throw new e.ElectroError(
|
|
4630
|
+
e.ErrorCodes.InvalidIndexCompositeAttributes,
|
|
4631
|
+
`The ${accessPattern} Access pattern is defined as a "${indexType}" index, but a Partition Key is defined without a HasKey field mapping.`,
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4352
4636
|
let collection = index.collection || "";
|
|
4353
4637
|
let customFacets = {
|
|
4354
4638
|
pk: false,
|
|
@@ -4522,13 +4806,16 @@ class Entity {
|
|
|
4522
4806
|
facets.attributes = [...facets.attributes, ...attributes];
|
|
4523
4807
|
facets.projections = [...facets.projections, ...projections];
|
|
4524
4808
|
|
|
4525
|
-
|
|
4809
|
+
if (definition.type !== IndexTypes.composite) {
|
|
4810
|
+
facets.fields.push(pk.field);
|
|
4811
|
+
}
|
|
4526
4812
|
|
|
4527
4813
|
facets.byIndex[indexName] = {
|
|
4528
4814
|
customFacets,
|
|
4529
4815
|
pk: pk.facets,
|
|
4530
4816
|
sk: sk.facets,
|
|
4531
4817
|
all: attributes,
|
|
4818
|
+
type: index.type,
|
|
4532
4819
|
collection: index.collection,
|
|
4533
4820
|
hasSortKeys: !!indexHasSortKeys[indexName],
|
|
4534
4821
|
hasSubCollections: !!indexHasSubCollections[indexName],
|
|
@@ -4538,110 +4825,111 @@ class Entity {
|
|
|
4538
4825
|
},
|
|
4539
4826
|
};
|
|
4540
4827
|
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
if (seenIndexFields[pk.field] !== undefined) {
|
|
4550
|
-
const definition = Object.values(facets.byField[pk.field]).find(
|
|
4551
|
-
(definition) => definition.index !== indexName,
|
|
4552
|
-
);
|
|
4553
|
-
|
|
4554
|
-
const definitionsMatch = validations.stringArrayMatch(
|
|
4555
|
-
pk.facets,
|
|
4556
|
-
definition.facets,
|
|
4557
|
-
);
|
|
4558
|
-
|
|
4559
|
-
if (!definitionsMatch) {
|
|
4560
|
-
throw new e.ElectroError(
|
|
4561
|
-
e.ErrorCodes.InconsistentIndexDefinition,
|
|
4562
|
-
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4563
|
-
accessPattern,
|
|
4564
|
-
)}' is defined with the composite attribute(s) ${u.commaSeparatedString(
|
|
4565
|
-
pk.facets,
|
|
4566
|
-
)}, but the accessPattern '${u.formatIndexNameForDisplay(
|
|
4567
|
-
definition.index,
|
|
4568
|
-
)}' defines this field with the composite attributes ${u.commaSeparatedString(
|
|
4569
|
-
definition.facets,
|
|
4570
|
-
)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
|
|
4571
|
-
);
|
|
4572
|
-
}
|
|
4573
|
-
|
|
4574
|
-
const keyTemplatesMatch = pk.template === definition.template
|
|
4575
|
-
|
|
4576
|
-
if (!keyTemplatesMatch) {
|
|
4577
|
-
throw new e.ElectroError(
|
|
4578
|
-
e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
|
|
4579
|
-
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4580
|
-
accessPattern,
|
|
4581
|
-
)}' is defined with the template ${pk.template || '(undefined)'}, but the accessPattern '${u.formatIndexNameForDisplay(
|
|
4582
|
-
definition.index,
|
|
4583
|
-
)}' defines this field with the key labels ${definition.template || '(undefined)'}'. Key fields must have the same template definitions across all indexes they are involved with`,
|
|
4584
|
-
);
|
|
4828
|
+
if (definition.type !== IndexTypes.composite) {
|
|
4829
|
+
facets.byField = facets.byField || {};
|
|
4830
|
+
facets.byField[pk.field] = facets.byField[pk.field] || {};
|
|
4831
|
+
facets.byField[pk.field][indexName] = pk;
|
|
4832
|
+
if (sk.field) {
|
|
4833
|
+
facets.byField[sk.field] = facets.byField[sk.field] || {};
|
|
4834
|
+
facets.byField[sk.field][indexName] = sk;
|
|
4585
4835
|
}
|
|
4586
4836
|
|
|
4587
|
-
seenIndexFields[pk.field]
|
|
4588
|
-
|
|
4589
|
-
seenIndexFields[pk.field] = [];
|
|
4590
|
-
seenIndexFields[pk.field].push({ accessPattern, type: "pk" });
|
|
4591
|
-
}
|
|
4592
|
-
|
|
4593
|
-
if (sk.field) {
|
|
4594
|
-
if (sk.field === pk.field) {
|
|
4595
|
-
throw new e.ElectroError(
|
|
4596
|
-
e.ErrorCodes.DuplicateIndexFields,
|
|
4597
|
-
`The Access Pattern '${u.formatIndexNameForDisplay(
|
|
4598
|
-
accessPattern,
|
|
4599
|
-
)}' references the field '${
|
|
4600
|
-
sk.field
|
|
4601
|
-
}' as the field name for both the PK and SK. Fields used for indexes need to be unique to avoid conflicts.`,
|
|
4602
|
-
);
|
|
4603
|
-
} else if (seenIndexFields[sk.field] !== undefined) {
|
|
4604
|
-
const definition = Object.values(facets.byField[sk.field]).find(
|
|
4837
|
+
if (seenIndexFields[pk.field] !== undefined) {
|
|
4838
|
+
const definition = Object.values(facets.byField[pk.field]).find(
|
|
4605
4839
|
(definition) => definition.index !== indexName,
|
|
4606
4840
|
);
|
|
4607
4841
|
|
|
4608
4842
|
const definitionsMatch = validations.stringArrayMatch(
|
|
4609
|
-
|
|
4843
|
+
pk.facets,
|
|
4610
4844
|
definition.facets,
|
|
4611
|
-
)
|
|
4612
|
-
|
|
4845
|
+
);
|
|
4613
4846
|
if (!definitionsMatch) {
|
|
4614
4847
|
throw new e.ElectroError(
|
|
4615
|
-
e.ErrorCodes.
|
|
4616
|
-
`
|
|
4848
|
+
e.ErrorCodes.InconsistentIndexDefinition,
|
|
4849
|
+
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4617
4850
|
accessPattern,
|
|
4618
4851
|
)}' is defined with the composite attribute(s) ${u.commaSeparatedString(
|
|
4619
|
-
|
|
4620
|
-
)}, but the
|
|
4621
|
-
definition.
|
|
4852
|
+
pk.facets,
|
|
4853
|
+
)}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4854
|
+
definition.accessPattern,
|
|
4622
4855
|
)}' defines this field with the composite attributes ${u.commaSeparatedString(
|
|
4623
4856
|
definition.facets,
|
|
4624
4857
|
)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
|
|
4625
4858
|
);
|
|
4626
4859
|
}
|
|
4627
4860
|
|
|
4628
|
-
const keyTemplatesMatch =
|
|
4861
|
+
const keyTemplatesMatch = pk.template === definition.template
|
|
4629
4862
|
|
|
4630
4863
|
if (!keyTemplatesMatch) {
|
|
4631
4864
|
throw new e.ElectroError(
|
|
4632
4865
|
e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
|
|
4633
|
-
`
|
|
4866
|
+
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4634
4867
|
accessPattern,
|
|
4635
|
-
)}' is defined with the template ${
|
|
4636
|
-
definition.
|
|
4868
|
+
)}' is defined with the template ${pk.template || '(undefined)'}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4869
|
+
definition.accessPattern,
|
|
4637
4870
|
)}' defines this field with the key labels ${definition.template || '(undefined)'}'. Key fields must have the same template definitions across all indexes they are involved with`,
|
|
4638
4871
|
);
|
|
4639
4872
|
}
|
|
4640
4873
|
|
|
4641
|
-
seenIndexFields[
|
|
4874
|
+
seenIndexFields[pk.field].push({ accessPattern, type: "pk" });
|
|
4642
4875
|
} else {
|
|
4643
|
-
seenIndexFields[
|
|
4644
|
-
seenIndexFields[
|
|
4876
|
+
seenIndexFields[pk.field] = [];
|
|
4877
|
+
seenIndexFields[pk.field].push({ accessPattern, type: "pk" });
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4880
|
+
if (sk.field) {
|
|
4881
|
+
if (sk.field === pk.field) {
|
|
4882
|
+
throw new e.ElectroError(
|
|
4883
|
+
e.ErrorCodes.DuplicateIndexFields,
|
|
4884
|
+
`The Access Pattern '${u.formatIndexNameForDisplay(
|
|
4885
|
+
accessPattern,
|
|
4886
|
+
)}' references the field '${
|
|
4887
|
+
sk.field
|
|
4888
|
+
}' as the field name for both the PK and SK. Fields used for indexes need to be unique to avoid conflicts.`,
|
|
4889
|
+
);
|
|
4890
|
+
} else if (seenIndexFields[sk.field] !== undefined) {
|
|
4891
|
+
const definition = Object.values(facets.byField[sk.field]).find(
|
|
4892
|
+
(definition) => definition.index !== indexName,
|
|
4893
|
+
);
|
|
4894
|
+
|
|
4895
|
+
const definitionsMatch = validations.stringArrayMatch(
|
|
4896
|
+
sk.facets,
|
|
4897
|
+
definition.facets,
|
|
4898
|
+
)
|
|
4899
|
+
|
|
4900
|
+
if (!definitionsMatch) {
|
|
4901
|
+
throw new e.ElectroError(
|
|
4902
|
+
e.ErrorCodes.DuplicateIndexFields,
|
|
4903
|
+
`Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4904
|
+
accessPattern,
|
|
4905
|
+
)}' is defined with the composite attribute(s) ${u.commaSeparatedString(
|
|
4906
|
+
sk.facets,
|
|
4907
|
+
)}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4908
|
+
definition.accessPattern,
|
|
4909
|
+
)}' defines this field with the composite attributes ${u.commaSeparatedString(
|
|
4910
|
+
definition.facets,
|
|
4911
|
+
)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
|
|
4912
|
+
);
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
const keyTemplatesMatch = sk.template === definition.template
|
|
4916
|
+
|
|
4917
|
+
if (!keyTemplatesMatch) {
|
|
4918
|
+
throw new e.ElectroError(
|
|
4919
|
+
e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
|
|
4920
|
+
`Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4921
|
+
accessPattern,
|
|
4922
|
+
)}' is defined with the template ${sk.template || '(undefined)'}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4923
|
+
definition.accessPattern,
|
|
4924
|
+
)}' defines this field with the key labels ${definition.template || '(undefined)'}'. Key fields must have the same template definitions across all indexes they are involved with`,
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
seenIndexFields[sk.field].push({ accessPattern, type: "sk" });
|
|
4929
|
+
} else {
|
|
4930
|
+
seenIndexFields[sk.field] = [];
|
|
4931
|
+
seenIndexFields[sk.field].push({ accessPattern, type: "sk" });
|
|
4932
|
+
}
|
|
4645
4933
|
}
|
|
4646
4934
|
}
|
|
4647
4935
|
|
|
@@ -4713,6 +5001,7 @@ class Entity {
|
|
|
4713
5001
|
subCollections,
|
|
4714
5002
|
indexHasSortKeys,
|
|
4715
5003
|
clusteredIndexes,
|
|
5004
|
+
compositeIndexes,
|
|
4716
5005
|
indexHasSubCollections,
|
|
4717
5006
|
indexes: normalized,
|
|
4718
5007
|
indexField: indexFieldTranslation,
|
|
@@ -4918,6 +5207,7 @@ class Entity {
|
|
|
4918
5207
|
subCollections,
|
|
4919
5208
|
indexCollection,
|
|
4920
5209
|
clusteredIndexes,
|
|
5210
|
+
compositeIndexes,
|
|
4921
5211
|
indexHasSortKeys,
|
|
4922
5212
|
indexAccessPattern,
|
|
4923
5213
|
indexHasSubCollections,
|
|
@@ -4991,6 +5281,7 @@ class Entity {
|
|
|
4991
5281
|
modelVersion,
|
|
4992
5282
|
subCollections,
|
|
4993
5283
|
lookup: {
|
|
5284
|
+
compositeIndexes,
|
|
4994
5285
|
clusteredIndexes,
|
|
4995
5286
|
indexHasSortKeys,
|
|
4996
5287
|
indexHasSubCollections,
|