electrodb 3.6.2 → 3.7.1
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 +19 -7
- package/package.json +1 -1
- package/src/clauses.js +92 -61
- package/src/entity.js +451 -155
- 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 = {}) {
|
|
@@ -516,7 +516,8 @@ class Entity {
|
|
|
516
516
|
);
|
|
517
517
|
};
|
|
518
518
|
const dynamoDBMethod = MethodTypeTranslation[method];
|
|
519
|
-
|
|
519
|
+
const client = config.client || this.client;
|
|
520
|
+
return client[dynamoDBMethod](params)
|
|
520
521
|
.promise()
|
|
521
522
|
.then((results) => {
|
|
522
523
|
notifyQuery();
|
|
@@ -1035,12 +1036,13 @@ class Entity {
|
|
|
1035
1036
|
let keys = {};
|
|
1036
1037
|
const secondaryIndexStrictMode =
|
|
1037
1038
|
options.strict === "all" || options.strict === "pk" ? "pk" : "none";
|
|
1038
|
-
for (const
|
|
1039
|
+
for (const index of Object.values(this.model.indexes)) {
|
|
1040
|
+
const indexName = index.index;
|
|
1039
1041
|
const indexKeys = this._fromCompositeToKeysByIndex(
|
|
1040
|
-
{ indexName
|
|
1042
|
+
{ indexName, provided },
|
|
1041
1043
|
{
|
|
1042
1044
|
strict:
|
|
1043
|
-
|
|
1045
|
+
indexName === TableIndex ? options.strict : secondaryIndexStrictMode,
|
|
1044
1046
|
},
|
|
1045
1047
|
);
|
|
1046
1048
|
if (indexKeys) {
|
|
@@ -1224,21 +1226,28 @@ class Entity {
|
|
|
1224
1226
|
) {
|
|
1225
1227
|
let allKeys = {};
|
|
1226
1228
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1229
|
+
if (this._getIndexType(indexName) === IndexTypes.composite) {
|
|
1230
|
+
const item = this.model.schema.translateFromFields(provided);
|
|
1231
|
+
allKeys = {
|
|
1232
|
+
...this._findFacets(item, this.model.facets.byIndex[indexName].pk),
|
|
1233
|
+
...this._findFacets(item, this.model.facets.byIndex[indexName].sk),
|
|
1234
|
+
}
|
|
1235
|
+
} else {
|
|
1236
|
+
const indexKeys = this._deconstructIndex({
|
|
1237
|
+
index: indexName,
|
|
1238
|
+
keys: provided,
|
|
1239
|
+
});
|
|
1240
|
+
if (!indexKeys) {
|
|
1241
|
+
throw new e.ElectroError(
|
|
1242
|
+
e.ErrorCodes.InvalidConversionKeysProvided,
|
|
1243
|
+
`Provided keys did not include valid properties for the index "${indexName}"`,
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
allKeys = {
|
|
1247
|
+
...indexKeys,
|
|
1248
|
+
};
|
|
1236
1249
|
}
|
|
1237
1250
|
|
|
1238
|
-
allKeys = {
|
|
1239
|
-
...indexKeys,
|
|
1240
|
-
};
|
|
1241
|
-
|
|
1242
1251
|
let tableKeys;
|
|
1243
1252
|
if (indexName !== TableIndex) {
|
|
1244
1253
|
tableKeys = this._deconstructIndex({ index: TableIndex, keys: provided });
|
|
@@ -1283,10 +1292,17 @@ class Entity {
|
|
|
1283
1292
|
);
|
|
1284
1293
|
}
|
|
1285
1294
|
|
|
1295
|
+
_getIndexType(indexName) {
|
|
1296
|
+
return this.model.facets.byIndex[indexName].type;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1286
1299
|
_trimKeysToIndex({ indexName = TableIndex, provided }) {
|
|
1287
1300
|
if (!provided) {
|
|
1288
1301
|
return null;
|
|
1289
1302
|
}
|
|
1303
|
+
if (this.model.facets.byIndex[indexName].type === IndexTypes.composite) {
|
|
1304
|
+
return provided;
|
|
1305
|
+
}
|
|
1290
1306
|
|
|
1291
1307
|
const pkName = this.model.translations.keys[indexName].pk;
|
|
1292
1308
|
const skName = this.model.translations.keys[indexName].sk;
|
|
@@ -1315,13 +1331,17 @@ class Entity {
|
|
|
1315
1331
|
);
|
|
1316
1332
|
}
|
|
1317
1333
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1334
|
+
if (this._getIndexType(indexName) === IndexTypes.composite) {
|
|
1335
|
+
const item = this.model.schema.translateFromFields(provided);
|
|
1336
|
+
return this.model.facets.byIndex[indexName].pk.every((attr) => item[attr] !== undefined);
|
|
1337
|
+
} else {
|
|
1338
|
+
const pkName = this.model.translations.keys[indexName].pk;
|
|
1339
|
+
const skName = this.model.translations.keys[indexName].sk;
|
|
1340
|
+
return (
|
|
1341
|
+
provided[pkName] !== undefined &&
|
|
1342
|
+
(!skName || provided[skName] !== undefined)
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1325
1345
|
}
|
|
1326
1346
|
|
|
1327
1347
|
_formatReturnPager(config, lastEvaluatedKey) {
|
|
@@ -1585,12 +1605,19 @@ class Entity {
|
|
|
1585
1605
|
|
|
1586
1606
|
_constructPagerIndex(index = TableIndex, item, options = {}) {
|
|
1587
1607
|
let pkAttributes = options.relaxedPk
|
|
1588
|
-
? item
|
|
1608
|
+
? this._findFacets(item, this.model.facets.byIndex[index].pk)
|
|
1589
1609
|
: this._expectFacets(item, this.model.facets.byIndex[index].pk);
|
|
1590
1610
|
let skAttributes = options.relaxedSk
|
|
1591
|
-
? item
|
|
1611
|
+
? this._findFacets(item, this.model.facets.byIndex[index].sk)
|
|
1592
1612
|
: this._expectFacets(item, this.model.facets.byIndex[index].sk);
|
|
1593
1613
|
|
|
1614
|
+
if (this._getIndexType(index) === IndexTypes.composite) {
|
|
1615
|
+
return this.model.schema.translateToFields({
|
|
1616
|
+
...pkAttributes,
|
|
1617
|
+
...skAttributes,
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1594
1621
|
let keys = this._makeIndexKeys({
|
|
1595
1622
|
index,
|
|
1596
1623
|
pkAttributes,
|
|
@@ -1905,6 +1932,10 @@ class Entity {
|
|
|
1905
1932
|
config.hydrator = option.hydrator;
|
|
1906
1933
|
}
|
|
1907
1934
|
|
|
1935
|
+
if (option.client !== undefined) {
|
|
1936
|
+
config.client = c.normalizeClient(option.client);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1908
1939
|
if (option._includeOnResponseItem) {
|
|
1909
1940
|
config._includeOnResponseItem = {
|
|
1910
1941
|
...config._includeOnResponseItem,
|
|
@@ -2257,20 +2288,20 @@ class Entity {
|
|
|
2257
2288
|
return key;
|
|
2258
2289
|
}
|
|
2259
2290
|
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
}
|
|
2291
|
+
applyIdentifierExpressionState(expressionState, alias) {
|
|
2292
|
+
const name = this.getName();
|
|
2293
|
+
const version = this.getVersion();
|
|
2294
|
+
const nameRef = expressionState.setName({}, this.identifiers.entity, this.identifiers.entity);
|
|
2295
|
+
const versionRef = expressionState.setName({}, this.identifiers.version, this.identifiers.version);
|
|
2296
|
+
const nameVal = expressionState.setValue(
|
|
2297
|
+
`${this.identifiers.entity}_${alias || name}`,
|
|
2298
|
+
name,
|
|
2299
|
+
);
|
|
2300
|
+
const versionVal = expressionState.setValue(
|
|
2301
|
+
`${this.identifiers.version}_${alias || name}`,
|
|
2302
|
+
version,
|
|
2303
|
+
);
|
|
2304
|
+
return `(${nameRef.expression} = ${nameVal} AND ${versionRef.expression} = ${versionVal})`;
|
|
2274
2305
|
}
|
|
2275
2306
|
|
|
2276
2307
|
/* istanbul ignore next */
|
|
@@ -2682,7 +2713,7 @@ class Entity {
|
|
|
2682
2713
|
}
|
|
2683
2714
|
expressions.UpdateExpression = `${operation.toUpperCase()} ${expressions.UpdateExpression.join(
|
|
2684
2715
|
", ",
|
|
2685
|
-
)}
|
|
2716
|
+
)}`.trim();
|
|
2686
2717
|
return expressions;
|
|
2687
2718
|
}
|
|
2688
2719
|
|
|
@@ -2709,61 +2740,236 @@ class Entity {
|
|
|
2709
2740
|
}
|
|
2710
2741
|
}
|
|
2711
2742
|
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2743
|
+
|
|
2744
|
+
_compositeQueryParams(state = {}, options = {}) {
|
|
2745
|
+
// todo: review "_consolidateQueryFacets"
|
|
2746
|
+
const consolidated = this._consolidateQueryFacets(
|
|
2747
|
+
state.query.keys.sk,
|
|
2748
|
+
) || [];
|
|
2749
|
+
|
|
2750
|
+
const pkAttributes = state.query.keys.pk;
|
|
2751
|
+
const skAttributes = consolidated[0] || {};
|
|
2752
|
+
|
|
2753
|
+
// provided has length, isArray?
|
|
2754
|
+
const provided = state.query.keys.provided;
|
|
2755
|
+
const all = state.query.facets.all || [];
|
|
2756
|
+
const queryType = state.query.type;
|
|
2757
|
+
const expressionState = new ExpressionState({ prefix: "k_" });
|
|
2758
|
+
|
|
2759
|
+
const expressions = [];
|
|
2760
|
+
if (queryType === QueryTypes.between) {
|
|
2761
|
+
for (const [name, value] of Object.entries(pkAttributes)) {
|
|
2762
|
+
const field = this.model.schema.getFieldName(name);
|
|
2763
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2764
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2765
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2766
|
+
}
|
|
2767
|
+
let is = {}
|
|
2768
|
+
let start = {}
|
|
2769
|
+
let end = {};
|
|
2770
|
+
(state.query.keys.sk || []).forEach(({type, facets}) => {
|
|
2771
|
+
if (type === QueryTypes.is || type === QueryTypes.composite_collection) {
|
|
2772
|
+
is = facets;
|
|
2773
|
+
} else if (type === QueryTypes.between) {
|
|
2774
|
+
start = facets;
|
|
2775
|
+
} else if (type === QueryTypes.and) {
|
|
2776
|
+
end = facets;
|
|
2777
|
+
} else {
|
|
2778
|
+
// todo: improve error handling
|
|
2779
|
+
throw new Error('Internal error: Invalid sort key type in composite between query');
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2782
|
+
|
|
2783
|
+
let lastFound;
|
|
2784
|
+
const skNames = state.query.facets.sk || [];
|
|
2785
|
+
for (const name of skNames) {
|
|
2786
|
+
if (is[name] !== undefined) {
|
|
2787
|
+
lastFound = name;
|
|
2788
|
+
} else if (start[name] !== undefined && end[name] !== undefined) {
|
|
2789
|
+
lastFound = name;
|
|
2790
|
+
} else if (start[name] !== undefined || end[name] !== undefined) {
|
|
2791
|
+
throw new e.ElectroError(
|
|
2792
|
+
e.ErrorCodes.InvalidQueryParameters,
|
|
2793
|
+
`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.`
|
|
2794
|
+
);
|
|
2795
|
+
} else {
|
|
2796
|
+
break;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
for (let i = 0; i < skNames.length; i++) {
|
|
2801
|
+
const name = skNames[i];
|
|
2802
|
+
if (lastFound === name) {
|
|
2803
|
+
const startValue = start[name];
|
|
2804
|
+
const endValue = end[name];
|
|
2805
|
+
const field = this.model.schema.getFieldName(name);
|
|
2806
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2807
|
+
const startValueRef = expressionState.setValue(name, startValue);
|
|
2808
|
+
const endValueRef = expressionState.setValue(name, endValue);
|
|
2809
|
+
expressions.push(`${nameRef.expression} BETWEEN ${startValueRef} AND ${endValueRef}`);
|
|
2810
|
+
} else if (is[name] !== undefined) {
|
|
2811
|
+
const value = is[name];
|
|
2812
|
+
const field = this.model.schema.getFieldName(name);
|
|
2813
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2814
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2815
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2816
|
+
} else if (start[name] !== undefined && end[name] !== undefined) {
|
|
2817
|
+
if (start[name] !== end[name]) {
|
|
2818
|
+
throw new e.ElectroError(
|
|
2819
|
+
e.ErrorCodes.InvalidQueryParameters,
|
|
2820
|
+
`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.`
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
const value = start[name];
|
|
2824
|
+
const field = this.model.schema.getFieldName(name);
|
|
2825
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2826
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2827
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
} else {
|
|
2831
|
+
const attrs = [];
|
|
2832
|
+
for (const { type, name } of all) {
|
|
2833
|
+
const value = type === "pk" ? pkAttributes[name] : skAttributes[name];
|
|
2834
|
+
if (value === undefined) {
|
|
2835
|
+
break;
|
|
2836
|
+
}
|
|
2837
|
+
attrs.push({type, name, value});
|
|
2838
|
+
}
|
|
2839
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
2840
|
+
const { type, name, value } = attrs[i];
|
|
2841
|
+
const field = this.model.schema.getFieldName(name);
|
|
2842
|
+
const nameRef = expressionState.setName({}, name, field);
|
|
2843
|
+
const valueRef = expressionState.setValue(name, value);
|
|
2844
|
+
const shouldApplyEq = !(type === "sk" && i === attrs.length - 1)
|
|
2845
|
+
if (shouldApplyEq) {
|
|
2846
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2847
|
+
continue;
|
|
2848
|
+
}
|
|
2849
|
+
switch (queryType) {
|
|
2850
|
+
case QueryTypes.is:
|
|
2851
|
+
case QueryTypes.eq:
|
|
2852
|
+
case QueryTypes.collection:
|
|
2853
|
+
case QueryTypes.composite_collection:
|
|
2854
|
+
expressions.push(`${nameRef.expression} = ${valueRef}`);
|
|
2855
|
+
break;
|
|
2856
|
+
case QueryTypes.begins:
|
|
2857
|
+
expressions.push(`begins_with(${nameRef.expression}, ${valueRef})`);
|
|
2858
|
+
break;
|
|
2859
|
+
case QueryTypes.gt:
|
|
2860
|
+
expressions.push(`${nameRef.expression} > ${valueRef}`);
|
|
2861
|
+
break;
|
|
2862
|
+
case QueryTypes.gte:
|
|
2863
|
+
expressions.push(`${nameRef.expression} >= ${valueRef}`);
|
|
2864
|
+
break;
|
|
2865
|
+
case QueryTypes.lt:
|
|
2866
|
+
expressions.push(`${nameRef.expression} < ${valueRef}`);
|
|
2867
|
+
break;
|
|
2868
|
+
case QueryTypes.lte:
|
|
2869
|
+
expressions.push(`${nameRef.expression} <= ${valueRef}`);
|
|
2870
|
+
break;
|
|
2871
|
+
case QueryTypes.between: {
|
|
2872
|
+
const second = consolidated[consolidated.length - 1];
|
|
2873
|
+
const value2 = second[name];
|
|
2874
|
+
const valueRef2 = expressionState.setValue(name, value2);
|
|
2875
|
+
expressions.push(`${nameRef.expression} BETWEEN ${valueRef} AND ${valueRef2}`);
|
|
2876
|
+
break;
|
|
2877
|
+
}
|
|
2878
|
+
// todo: clean up here
|
|
2879
|
+
case QueryTypes.clustered_collection:
|
|
2880
|
+
default:
|
|
2881
|
+
// todo: improve error handling
|
|
2882
|
+
throw new Error('Not supported')
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
const filter = state.query.filter[ExpressionTypes.FilterExpression];
|
|
2888
|
+
const customExpressions = {
|
|
2889
|
+
names: (state.query.options.expressions && state.query.options.expressions.names) || {},
|
|
2890
|
+
values: (state.query.options.expressions && state.query.options.expressions.values) || {},
|
|
2891
|
+
expression: (state.query.options.expressions && state.query.options.expressions.expression) || "",
|
|
2892
|
+
};
|
|
2893
|
+
|
|
2894
|
+
// identifiers are added via custom expressions on collection queries inside `clauses/handleNonIsolatedCollection`
|
|
2895
|
+
// Don't duplicate filters if they are provided.
|
|
2896
|
+
const identifierExpression = !options.ignoreOwnership && !customExpressions.expression ? this.applyIdentifierExpressionState(expressionState) : '';
|
|
2897
|
+
|
|
2898
|
+
const params = {
|
|
2899
|
+
IndexName: state.query.index,
|
|
2900
|
+
KeyConditionExpression: expressions.join(' AND '),
|
|
2901
|
+
TableName: this.getTableName(),
|
|
2902
|
+
ExpressionAttributeNames: this._mergeExpressionsAttributes(
|
|
2903
|
+
filter.getNames(),
|
|
2904
|
+
expressionState.getNames(),
|
|
2905
|
+
customExpressions.names,
|
|
2906
|
+
),
|
|
2907
|
+
ExpressionAttributeValues: this._mergeExpressionsAttributes(
|
|
2908
|
+
filter.getValues(),
|
|
2909
|
+
expressionState.getValues(),
|
|
2910
|
+
customExpressions.values,
|
|
2911
|
+
),
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
let filerExpressions = [customExpressions.expression || "", filter.build(), identifierExpression]
|
|
2915
|
+
.map(s => s.trim())
|
|
2916
|
+
.filter(Boolean)
|
|
2917
|
+
.join(" AND ");
|
|
2918
|
+
|
|
2919
|
+
if (filerExpressions.length) {
|
|
2920
|
+
params.FilterExpression = filerExpressions;
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
return params;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
_makeQueryParams(state = {}, options = {}, indexKeys) {
|
|
2716
2927
|
switch (state.query.type) {
|
|
2717
2928
|
case QueryTypes.is:
|
|
2718
|
-
|
|
2929
|
+
return this._makeIsQueryParams(
|
|
2719
2930
|
state.query,
|
|
2720
2931
|
state.query.index,
|
|
2721
2932
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2722
2933
|
indexKeys.pk,
|
|
2723
2934
|
...indexKeys.sk,
|
|
2724
2935
|
);
|
|
2725
|
-
break;
|
|
2726
2936
|
case QueryTypes.begins:
|
|
2727
|
-
|
|
2937
|
+
return this._makeBeginsWithQueryParams(
|
|
2728
2938
|
state.query.options,
|
|
2729
2939
|
state.query.index,
|
|
2730
2940
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2731
2941
|
indexKeys.pk,
|
|
2732
2942
|
...indexKeys.sk,
|
|
2733
2943
|
);
|
|
2734
|
-
break;
|
|
2735
2944
|
case QueryTypes.collection:
|
|
2736
|
-
|
|
2945
|
+
return this._makeBeginsWithQueryParams(
|
|
2737
2946
|
state.query.options,
|
|
2738
2947
|
state.query.index,
|
|
2739
2948
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2740
2949
|
indexKeys.pk,
|
|
2741
2950
|
this._getCollectionSk(state.query.collection),
|
|
2742
2951
|
);
|
|
2743
|
-
break;
|
|
2744
2952
|
case QueryTypes.clustered_collection:
|
|
2745
|
-
|
|
2953
|
+
return this._makeBeginsWithQueryParams(
|
|
2746
2954
|
state.query.options,
|
|
2747
2955
|
state.query.index,
|
|
2748
2956
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2749
2957
|
indexKeys.pk,
|
|
2750
2958
|
...indexKeys.sk,
|
|
2751
2959
|
);
|
|
2752
|
-
break;
|
|
2753
2960
|
case QueryTypes.between:
|
|
2754
|
-
|
|
2961
|
+
return this._makeBetweenQueryParams(
|
|
2755
2962
|
state.query.options,
|
|
2756
2963
|
state.query.index,
|
|
2757
2964
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
2758
2965
|
indexKeys.pk,
|
|
2759
2966
|
...indexKeys.sk,
|
|
2760
2967
|
);
|
|
2761
|
-
break;
|
|
2762
2968
|
case QueryTypes.gte:
|
|
2763
2969
|
case QueryTypes.gt:
|
|
2764
2970
|
case QueryTypes.lte:
|
|
2765
2971
|
case QueryTypes.lt:
|
|
2766
|
-
|
|
2972
|
+
return this._makeComparisonQueryParams(
|
|
2767
2973
|
state.query.index,
|
|
2768
2974
|
state.query.type,
|
|
2769
2975
|
state.query.filter[ExpressionTypes.FilterExpression],
|
|
@@ -2771,10 +2977,20 @@ class Entity {
|
|
|
2771
2977
|
options,
|
|
2772
2978
|
state.query.options,
|
|
2773
2979
|
);
|
|
2774
|
-
break;
|
|
2775
2980
|
default:
|
|
2776
2981
|
throw new Error(`Invalid query type: ${state.query.type}`);
|
|
2777
2982
|
}
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
/* istanbul ignore next */
|
|
2986
|
+
_queryParams(state = {}, options = {}) {
|
|
2987
|
+
const indexKeys = this._makeQueryKeys(state, options);
|
|
2988
|
+
let parameters;
|
|
2989
|
+
if (state.query.options.indexType !== IndexTypes.composite) {
|
|
2990
|
+
parameters = this._makeQueryParams(state, options, indexKeys);
|
|
2991
|
+
} else {
|
|
2992
|
+
parameters = this._compositeQueryParams(state, options);
|
|
2993
|
+
}
|
|
2778
2994
|
|
|
2779
2995
|
const appliedParameters = this._applyParameterOptions({
|
|
2780
2996
|
params: parameters,
|
|
@@ -3064,8 +3280,12 @@ class Entity {
|
|
|
3064
3280
|
_makeKeysFromAttributes(indexes, attributes, conditions) {
|
|
3065
3281
|
let indexKeys = {};
|
|
3066
3282
|
for (let [index, keyTypes] of Object.entries(indexes)) {
|
|
3283
|
+
if (this.model.lookup.compositeIndexes.has(index)) {
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3067
3287
|
const shouldMakeKeys =
|
|
3068
|
-
!this._indexConditionIsDefined(index) || conditions[index];
|
|
3288
|
+
(!this._indexConditionIsDefined(index) || conditions[index]);
|
|
3069
3289
|
if (!shouldMakeKeys && index !== TableIndex) {
|
|
3070
3290
|
continue;
|
|
3071
3291
|
}
|
|
@@ -3285,6 +3505,11 @@ class Entity {
|
|
|
3285
3505
|
if (attributes[attribute] !== undefined) {
|
|
3286
3506
|
facets[attribute] = attributes[attribute];
|
|
3287
3507
|
indexes.forEach((definition) => {
|
|
3508
|
+
// composite indexes do not have keys
|
|
3509
|
+
if (definition.type === IndexTypes.composite) {
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3288
3513
|
const { index, type } = definition;
|
|
3289
3514
|
impactedIndexes[index] = impactedIndexes[index] || {};
|
|
3290
3515
|
impactedIndexes[index][type] = impactedIndexes[index][type] || [];
|
|
@@ -3303,9 +3528,12 @@ class Entity {
|
|
|
3303
3528
|
|
|
3304
3529
|
// 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
3530
|
if (utilizeIncludedOnlyIndexes) {
|
|
3306
|
-
for (const [index, { pk, sk }] of Object.entries(
|
|
3531
|
+
for (const [index, { pk, sk, type }] of Object.entries(
|
|
3307
3532
|
this.model.facets.byIndex,
|
|
3308
3533
|
)) {
|
|
3534
|
+
if (type === IndexTypes.composite) {
|
|
3535
|
+
continue;
|
|
3536
|
+
}
|
|
3309
3537
|
// The main table index is handled somewhere else (messy I know), and we only want to do this processing if an
|
|
3310
3538
|
// index condition is defined for backwards compatibility. Backwards compatibility is not required for this
|
|
3311
3539
|
// change, but I have paranoid concerns of breaking changes around sparse indexes.
|
|
@@ -3367,10 +3595,12 @@ class Entity {
|
|
|
3367
3595
|
}
|
|
3368
3596
|
}
|
|
3369
3597
|
|
|
3370
|
-
let indexesWithMissingComposites =
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3598
|
+
let indexesWithMissingComposites = [];
|
|
3599
|
+
for (const [index, definition] of Object.entries(this.model.facets.byIndex)) {
|
|
3600
|
+
const { pk, sk, type } = definition;
|
|
3601
|
+
if (type === IndexTypes.composite) {
|
|
3602
|
+
continue;
|
|
3603
|
+
}
|
|
3374
3604
|
let impacted = impactedIndexes[index];
|
|
3375
3605
|
let impact = {
|
|
3376
3606
|
index,
|
|
@@ -3408,8 +3638,8 @@ class Entity {
|
|
|
3408
3638
|
}
|
|
3409
3639
|
}
|
|
3410
3640
|
|
|
3411
|
-
|
|
3412
|
-
}
|
|
3641
|
+
indexesWithMissingComposites.push(impact);
|
|
3642
|
+
}
|
|
3413
3643
|
|
|
3414
3644
|
let incomplete = [];
|
|
3415
3645
|
for (const { index, missing, definition } of indexesWithMissingComposites) {
|
|
@@ -3536,7 +3766,11 @@ class Entity {
|
|
|
3536
3766
|
}
|
|
3537
3767
|
}
|
|
3538
3768
|
|
|
3539
|
-
|
|
3769
|
+
_findFacets(obj, properties) {
|
|
3770
|
+
return Object.fromEntries(this._findProperties(obj, properties));
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
_findProperties(obj, properties = []) {
|
|
3540
3774
|
return properties.map((name) => [name, obj[name]]);
|
|
3541
3775
|
}
|
|
3542
3776
|
|
|
@@ -3707,8 +3941,8 @@ class Entity {
|
|
|
3707
3941
|
e.ErrorCodes.IncompatibleKeyCasing,
|
|
3708
3942
|
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
3709
3943
|
tableIndex.index,
|
|
3710
|
-
)}' is defined with the casing ${keys.pk.casing}, but the
|
|
3711
|
-
previouslyDefinedPk.
|
|
3944
|
+
)}' is defined with the casing ${keys.pk.casing}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
3945
|
+
previouslyDefinedPk.definition.accessPattern,
|
|
3712
3946
|
)}' 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
3947
|
);
|
|
3714
3948
|
}
|
|
@@ -3723,8 +3957,8 @@ class Entity {
|
|
|
3723
3957
|
e.ErrorCodes.IncompatibleKeyCasing,
|
|
3724
3958
|
`Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
3725
3959
|
tableIndex.index,
|
|
3726
|
-
)}' is defined with the casing ${keys.sk.casing}, but the
|
|
3727
|
-
previouslyDefinedSk.
|
|
3960
|
+
)}' is defined with the casing ${keys.sk.casing}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
3961
|
+
previouslyDefinedSk.definition.accessPattern,
|
|
3728
3962
|
)}' 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
3963
|
);
|
|
3730
3964
|
}
|
|
@@ -3832,7 +4066,6 @@ class Entity {
|
|
|
3832
4066
|
this.model.facets.labels[index] &&
|
|
3833
4067
|
Array.isArray(this.model.facets.labels[index].sk);
|
|
3834
4068
|
let labels = hasLabels ? this.model.facets.labels[index].sk : [];
|
|
3835
|
-
// const hasFacets = Object.keys(skFacet).length > 0;
|
|
3836
4069
|
let sortKey = this._makeKey(prefixes.sk, facets.sk, skFacet, labels, {
|
|
3837
4070
|
excludeLabelTail: true,
|
|
3838
4071
|
excludePostfix,
|
|
@@ -4277,6 +4510,7 @@ class Entity {
|
|
|
4277
4510
|
let indexHasSortKeys = {};
|
|
4278
4511
|
let indexHasSubCollections = {};
|
|
4279
4512
|
let clusteredIndexes = new Set();
|
|
4513
|
+
let compositeIndexes = new Set();
|
|
4280
4514
|
let indexAccessPatternTransaction = {
|
|
4281
4515
|
fromAccessPatternToIndex: {},
|
|
4282
4516
|
fromIndexToAccessPattern: {},
|
|
@@ -4320,9 +4554,48 @@ class Entity {
|
|
|
4320
4554
|
let conditionDefined = v.isFunction(index.condition);
|
|
4321
4555
|
let indexCondition = index.condition || (() => true);
|
|
4322
4556
|
|
|
4323
|
-
if (indexType ===
|
|
4557
|
+
if (indexType === IndexTypes.clustered) {
|
|
4558
|
+
// todo: make contents consistent with "compositeIndexes" below
|
|
4559
|
+
// this is not consistent with "compositeIndexes" (which uses the index name), this should be fixed in the future.
|
|
4324
4560
|
clusteredIndexes.add(accessPattern);
|
|
4561
|
+
} else if (indexType === IndexTypes.composite) {
|
|
4562
|
+
if (indexName === TableIndex) {
|
|
4563
|
+
throw new e.ElectroError(
|
|
4564
|
+
e.ErrorCodes.InvalidIndexDefinition,
|
|
4565
|
+
`The Access Pattern "${accessPattern}" cannot be defined as a composite index. AWS DynamoDB does not allow for composite indexes on the main table index.`,
|
|
4566
|
+
);
|
|
4567
|
+
}
|
|
4568
|
+
if (conditionDefined) {
|
|
4569
|
+
throw new e.ElectroError(
|
|
4570
|
+
e.ErrorCodes.InvalidIndexCondition,
|
|
4571
|
+
`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.`,
|
|
4572
|
+
);
|
|
4573
|
+
}
|
|
4574
|
+
if (index.scope !== undefined) {
|
|
4575
|
+
throw new e.ElectroError(
|
|
4576
|
+
e.ErrorCodes.InvalidIndexCondition,
|
|
4577
|
+
`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.`,
|
|
4578
|
+
);
|
|
4579
|
+
}
|
|
4580
|
+
if (index.pk.field !== undefined || (index.sk && index.sk.field !== undefined)) {
|
|
4581
|
+
throw new e.ElectroError(
|
|
4582
|
+
e.ErrorCodes.InvalidIndexDefinition,
|
|
4583
|
+
`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.`,
|
|
4584
|
+
);
|
|
4585
|
+
}
|
|
4586
|
+
// this is not consistent with "clusteredIndexes" (which uses the access pattern name), but it is more correct given the naming.
|
|
4587
|
+
compositeIndexes.add(indexName);
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
if (indexType !== IndexTypes.composite) {
|
|
4591
|
+
if (index.pk.field === undefined || (index.sk && index.sk.field === undefined)) {
|
|
4592
|
+
throw new e.ElectroError(
|
|
4593
|
+
e.ErrorCodes.InvalidIndexDefinition,
|
|
4594
|
+
`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.`,
|
|
4595
|
+
);
|
|
4596
|
+
}
|
|
4325
4597
|
}
|
|
4598
|
+
|
|
4326
4599
|
if (seenIndexes[indexName] !== undefined) {
|
|
4327
4600
|
if (indexName === TableIndex) {
|
|
4328
4601
|
throw new e.ElectroError(
|
|
@@ -4349,6 +4622,22 @@ class Entity {
|
|
|
4349
4622
|
)}', contains a collection definition without a defined SK. Collections can only be defined on indexes with a defined SK.`,
|
|
4350
4623
|
);
|
|
4351
4624
|
}
|
|
4625
|
+
|
|
4626
|
+
if (indexType !== IndexTypes.composite) {
|
|
4627
|
+
if (hasSk && index.sk.field === undefined) {
|
|
4628
|
+
throw new e.ElectroError(
|
|
4629
|
+
e.ErrorCodes.InvalidIndexCompositeAttributes,
|
|
4630
|
+
`The ${accessPattern} Access pattern is defined as a "${indexType}" index, but a Sort Key is defined without a Range Key field mapping.`,
|
|
4631
|
+
);
|
|
4632
|
+
}
|
|
4633
|
+
if (index.pk.field === undefined) {
|
|
4634
|
+
throw new e.ElectroError(
|
|
4635
|
+
e.ErrorCodes.InvalidIndexCompositeAttributes,
|
|
4636
|
+
`The ${accessPattern} Access pattern is defined as a "${indexType}" index, but a Partition Key is defined without a HasKey field mapping.`,
|
|
4637
|
+
);
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4352
4641
|
let collection = index.collection || "";
|
|
4353
4642
|
let customFacets = {
|
|
4354
4643
|
pk: false,
|
|
@@ -4522,13 +4811,16 @@ class Entity {
|
|
|
4522
4811
|
facets.attributes = [...facets.attributes, ...attributes];
|
|
4523
4812
|
facets.projections = [...facets.projections, ...projections];
|
|
4524
4813
|
|
|
4525
|
-
|
|
4814
|
+
if (definition.type !== IndexTypes.composite) {
|
|
4815
|
+
facets.fields.push(pk.field);
|
|
4816
|
+
}
|
|
4526
4817
|
|
|
4527
4818
|
facets.byIndex[indexName] = {
|
|
4528
4819
|
customFacets,
|
|
4529
4820
|
pk: pk.facets,
|
|
4530
4821
|
sk: sk.facets,
|
|
4531
4822
|
all: attributes,
|
|
4823
|
+
type: index.type,
|
|
4532
4824
|
collection: index.collection,
|
|
4533
4825
|
hasSortKeys: !!indexHasSortKeys[indexName],
|
|
4534
4826
|
hasSubCollections: !!indexHasSubCollections[indexName],
|
|
@@ -4538,110 +4830,111 @@ class Entity {
|
|
|
4538
4830
|
},
|
|
4539
4831
|
};
|
|
4540
4832
|
|
|
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
|
-
);
|
|
4833
|
+
if (definition.type !== IndexTypes.composite) {
|
|
4834
|
+
facets.byField = facets.byField || {};
|
|
4835
|
+
facets.byField[pk.field] = facets.byField[pk.field] || {};
|
|
4836
|
+
facets.byField[pk.field][indexName] = pk;
|
|
4837
|
+
if (sk.field) {
|
|
4838
|
+
facets.byField[sk.field] = facets.byField[sk.field] || {};
|
|
4839
|
+
facets.byField[sk.field][indexName] = sk;
|
|
4585
4840
|
}
|
|
4586
4841
|
|
|
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(
|
|
4842
|
+
if (seenIndexFields[pk.field] !== undefined) {
|
|
4843
|
+
const definition = Object.values(facets.byField[pk.field]).find(
|
|
4605
4844
|
(definition) => definition.index !== indexName,
|
|
4606
4845
|
);
|
|
4607
4846
|
|
|
4608
4847
|
const definitionsMatch = validations.stringArrayMatch(
|
|
4609
|
-
|
|
4848
|
+
pk.facets,
|
|
4610
4849
|
definition.facets,
|
|
4611
|
-
)
|
|
4612
|
-
|
|
4850
|
+
);
|
|
4613
4851
|
if (!definitionsMatch) {
|
|
4614
4852
|
throw new e.ElectroError(
|
|
4615
|
-
e.ErrorCodes.
|
|
4616
|
-
`
|
|
4853
|
+
e.ErrorCodes.InconsistentIndexDefinition,
|
|
4854
|
+
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4617
4855
|
accessPattern,
|
|
4618
4856
|
)}' is defined with the composite attribute(s) ${u.commaSeparatedString(
|
|
4619
|
-
|
|
4620
|
-
)}, but the
|
|
4621
|
-
definition.
|
|
4857
|
+
pk.facets,
|
|
4858
|
+
)}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4859
|
+
definition.accessPattern,
|
|
4622
4860
|
)}' defines this field with the composite attributes ${u.commaSeparatedString(
|
|
4623
4861
|
definition.facets,
|
|
4624
4862
|
)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
|
|
4625
4863
|
);
|
|
4626
4864
|
}
|
|
4627
4865
|
|
|
4628
|
-
const keyTemplatesMatch =
|
|
4866
|
+
const keyTemplatesMatch = pk.template === definition.template
|
|
4629
4867
|
|
|
4630
4868
|
if (!keyTemplatesMatch) {
|
|
4631
4869
|
throw new e.ElectroError(
|
|
4632
4870
|
e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
|
|
4633
|
-
`
|
|
4871
|
+
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4634
4872
|
accessPattern,
|
|
4635
|
-
)}' is defined with the template ${
|
|
4636
|
-
definition.
|
|
4873
|
+
)}' is defined with the template ${pk.template || '(undefined)'}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4874
|
+
definition.accessPattern,
|
|
4637
4875
|
)}' 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
4876
|
);
|
|
4639
4877
|
}
|
|
4640
4878
|
|
|
4641
|
-
seenIndexFields[
|
|
4879
|
+
seenIndexFields[pk.field].push({ accessPattern, type: "pk" });
|
|
4642
4880
|
} else {
|
|
4643
|
-
seenIndexFields[
|
|
4644
|
-
seenIndexFields[
|
|
4881
|
+
seenIndexFields[pk.field] = [];
|
|
4882
|
+
seenIndexFields[pk.field].push({ accessPattern, type: "pk" });
|
|
4883
|
+
}
|
|
4884
|
+
|
|
4885
|
+
if (sk.field) {
|
|
4886
|
+
if (sk.field === pk.field) {
|
|
4887
|
+
throw new e.ElectroError(
|
|
4888
|
+
e.ErrorCodes.DuplicateIndexFields,
|
|
4889
|
+
`The Access Pattern '${u.formatIndexNameForDisplay(
|
|
4890
|
+
accessPattern,
|
|
4891
|
+
)}' references the field '${
|
|
4892
|
+
sk.field
|
|
4893
|
+
}' as the field name for both the PK and SK. Fields used for indexes need to be unique to avoid conflicts.`,
|
|
4894
|
+
);
|
|
4895
|
+
} else if (seenIndexFields[sk.field] !== undefined) {
|
|
4896
|
+
const definition = Object.values(facets.byField[sk.field]).find(
|
|
4897
|
+
(definition) => definition.index !== indexName,
|
|
4898
|
+
);
|
|
4899
|
+
|
|
4900
|
+
const definitionsMatch = validations.stringArrayMatch(
|
|
4901
|
+
sk.facets,
|
|
4902
|
+
definition.facets,
|
|
4903
|
+
)
|
|
4904
|
+
|
|
4905
|
+
if (!definitionsMatch) {
|
|
4906
|
+
throw new e.ElectroError(
|
|
4907
|
+
e.ErrorCodes.DuplicateIndexFields,
|
|
4908
|
+
`Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4909
|
+
accessPattern,
|
|
4910
|
+
)}' is defined with the composite attribute(s) ${u.commaSeparatedString(
|
|
4911
|
+
sk.facets,
|
|
4912
|
+
)}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4913
|
+
definition.accessPattern,
|
|
4914
|
+
)}' defines this field with the composite attributes ${u.commaSeparatedString(
|
|
4915
|
+
definition.facets,
|
|
4916
|
+
)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
|
|
4917
|
+
);
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
const keyTemplatesMatch = sk.template === definition.template
|
|
4921
|
+
|
|
4922
|
+
if (!keyTemplatesMatch) {
|
|
4923
|
+
throw new e.ElectroError(
|
|
4924
|
+
e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
|
|
4925
|
+
`Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
|
|
4926
|
+
accessPattern,
|
|
4927
|
+
)}' is defined with the template ${sk.template || '(undefined)'}, but the Access Pattern '${u.formatIndexNameForDisplay(
|
|
4928
|
+
definition.accessPattern,
|
|
4929
|
+
)}' 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`,
|
|
4930
|
+
);
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
seenIndexFields[sk.field].push({ accessPattern, type: "sk" });
|
|
4934
|
+
} else {
|
|
4935
|
+
seenIndexFields[sk.field] = [];
|
|
4936
|
+
seenIndexFields[sk.field].push({ accessPattern, type: "sk" });
|
|
4937
|
+
}
|
|
4645
4938
|
}
|
|
4646
4939
|
}
|
|
4647
4940
|
|
|
@@ -4713,6 +5006,7 @@ class Entity {
|
|
|
4713
5006
|
subCollections,
|
|
4714
5007
|
indexHasSortKeys,
|
|
4715
5008
|
clusteredIndexes,
|
|
5009
|
+
compositeIndexes,
|
|
4716
5010
|
indexHasSubCollections,
|
|
4717
5011
|
indexes: normalized,
|
|
4718
5012
|
indexField: indexFieldTranslation,
|
|
@@ -4918,6 +5212,7 @@ class Entity {
|
|
|
4918
5212
|
subCollections,
|
|
4919
5213
|
indexCollection,
|
|
4920
5214
|
clusteredIndexes,
|
|
5215
|
+
compositeIndexes,
|
|
4921
5216
|
indexHasSortKeys,
|
|
4922
5217
|
indexAccessPattern,
|
|
4923
5218
|
indexHasSubCollections,
|
|
@@ -4991,6 +5286,7 @@ class Entity {
|
|
|
4991
5286
|
modelVersion,
|
|
4992
5287
|
subCollections,
|
|
4993
5288
|
lookup: {
|
|
5289
|
+
compositeIndexes,
|
|
4994
5290
|
clusteredIndexes,
|
|
4995
5291
|
indexHasSortKeys,
|
|
4996
5292
|
indexHasSubCollections,
|