@awsless/dynamodb-server 0.1.4 → 0.1.6
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/dist/index.d.ts +2 -0
- package/dist/index.js +287 -181
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -218,6 +218,8 @@ declare class Table {
|
|
|
218
218
|
getIndexKeySchema(indexName: string): KeySchemaElement[] | undefined;
|
|
219
219
|
private attributeEquals;
|
|
220
220
|
private compareAttributes;
|
|
221
|
+
private compareByKeySchema;
|
|
222
|
+
private queryBySchema;
|
|
221
223
|
getAllItems(): AttributeMap[];
|
|
222
224
|
clear(): void;
|
|
223
225
|
onStreamRecord(callback: StreamCallback): () => void;
|
package/dist/index.js
CHANGED
|
@@ -188,9 +188,15 @@ function validateAndCreate(store, input) {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
+
if (input.GlobalSecondaryIndexes) {
|
|
192
|
+
for (const gsi of input.GlobalSecondaryIndexes) {
|
|
193
|
+
validateSecondaryIndexKeySchema(gsi.IndexName, gsi.KeySchema, definedAttrs, true);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
191
196
|
if (input.LocalSecondaryIndexes) {
|
|
192
197
|
const tableHashKey = hashKeys[0].AttributeName;
|
|
193
198
|
for (const lsi of input.LocalSecondaryIndexes) {
|
|
199
|
+
validateSecondaryIndexKeySchema(lsi.IndexName, lsi.KeySchema, definedAttrs, false);
|
|
194
200
|
const lsiHashKey = lsi.KeySchema.find((k) => k.KeyType === "HASH");
|
|
195
201
|
if (!lsiHashKey || lsiHashKey.AttributeName !== tableHashKey) {
|
|
196
202
|
throw new ValidationException(
|
|
@@ -215,6 +221,43 @@ function createTable(store, input) {
|
|
|
215
221
|
const tableDescription = validateAndCreate(store, input);
|
|
216
222
|
return { TableDescription: tableDescription };
|
|
217
223
|
}
|
|
224
|
+
function validateSecondaryIndexKeySchema(indexName, keySchema, definedAttrs, isGlobal) {
|
|
225
|
+
if (!keySchema.length) {
|
|
226
|
+
throw new ValidationException(`Index ${indexName} must define a key schema`);
|
|
227
|
+
}
|
|
228
|
+
const hashKeys = keySchema.filter((k) => k.KeyType === "HASH");
|
|
229
|
+
const rangeKeys = keySchema.filter((k) => k.KeyType === "RANGE");
|
|
230
|
+
const maxHashKeys = isGlobal ? 4 : 1;
|
|
231
|
+
const maxRangeKeys = isGlobal ? 4 : 1;
|
|
232
|
+
if (hashKeys.length === 0 || hashKeys.length > maxHashKeys) {
|
|
233
|
+
throw new ValidationException(
|
|
234
|
+
isGlobal ? `Global secondary index ${indexName} must have between 1 and 4 partition key attributes` : `Local secondary index ${indexName} must have exactly one hash key`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (rangeKeys.length > maxRangeKeys) {
|
|
238
|
+
throw new ValidationException(
|
|
239
|
+
isGlobal ? `Global secondary index ${indexName} can have at most 4 sort key attributes` : `Local secondary index ${indexName} can have at most one range key`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (isGlobal && hashKeys.length + rangeKeys.length > 8) {
|
|
243
|
+
throw new ValidationException(`Global secondary index ${indexName} can have at most 8 key attributes`);
|
|
244
|
+
}
|
|
245
|
+
let seenRange = false;
|
|
246
|
+
for (const keyElement of keySchema) {
|
|
247
|
+
if (!definedAttrs.has(keyElement.AttributeName)) {
|
|
248
|
+
throw new ValidationException(
|
|
249
|
+
`Attribute ${keyElement.AttributeName} is specified in index ${indexName} but not in AttributeDefinitions`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (keyElement.KeyType === "RANGE") {
|
|
253
|
+
seenRange = true;
|
|
254
|
+
} else if (seenRange) {
|
|
255
|
+
throw new ValidationException(
|
|
256
|
+
`Index ${indexName} must list all partition key attributes before sort key attributes`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
218
261
|
|
|
219
262
|
// src/operations/delete-table.ts
|
|
220
263
|
function deleteTable(store, input) {
|
|
@@ -598,6 +641,25 @@ function evaluateCondition(expression, item, context) {
|
|
|
598
641
|
}
|
|
599
642
|
throw new ValidationException(`Unexpected token: ${token.type}`);
|
|
600
643
|
}
|
|
644
|
+
function parseSizeValue() {
|
|
645
|
+
consume("FUNCTION");
|
|
646
|
+
consume("LPAREN");
|
|
647
|
+
const pathToken = consume("PATH");
|
|
648
|
+
consume("RPAREN");
|
|
649
|
+
const segments = parsePath(pathToken.value, context.expressionAttributeNames);
|
|
650
|
+
const value = getValueAtPath(item, segments);
|
|
651
|
+
let size = 0;
|
|
652
|
+
if (value) {
|
|
653
|
+
if ("S" in value) size = value.S.length;
|
|
654
|
+
else if ("B" in value) size = value.B.length;
|
|
655
|
+
else if ("SS" in value) size = value.SS.length;
|
|
656
|
+
else if ("NS" in value) size = value.NS.length;
|
|
657
|
+
else if ("BS" in value) size = value.BS.length;
|
|
658
|
+
else if ("L" in value) size = value.L.length;
|
|
659
|
+
else if ("M" in value) size = Object.keys(value.M).length;
|
|
660
|
+
}
|
|
661
|
+
return size;
|
|
662
|
+
}
|
|
601
663
|
function parseFunction() {
|
|
602
664
|
const funcToken = consume("FUNCTION");
|
|
603
665
|
consume("LPAREN");
|
|
@@ -707,12 +769,18 @@ function evaluateCondition(expression, item, context) {
|
|
|
707
769
|
const nextToken = current();
|
|
708
770
|
if (nextToken?.type === "COMPARATOR") {
|
|
709
771
|
consume("COMPARATOR");
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
772
|
+
let rightNum;
|
|
773
|
+
if (current()?.type === "FUNCTION" && current()?.value === "size") {
|
|
774
|
+
rightNum = parseSizeValue();
|
|
775
|
+
} else {
|
|
776
|
+
const rightToken = consume();
|
|
777
|
+
const rightValue = resolveOperand2(rightToken);
|
|
778
|
+
if (!rightValue || !("N" in rightValue)) {
|
|
779
|
+
throw new ValidationException("Size comparison requires numeric operand");
|
|
780
|
+
}
|
|
781
|
+
rightNum = Number(rightValue.N);
|
|
714
782
|
}
|
|
715
|
-
return compareNumbers(size,
|
|
783
|
+
return compareNumbers(size, rightNum, nextToken.value);
|
|
716
784
|
}
|
|
717
785
|
return size > 0;
|
|
718
786
|
}
|
|
@@ -884,10 +952,30 @@ function getHashKey(keySchema) {
|
|
|
884
952
|
}
|
|
885
953
|
return hash.AttributeName;
|
|
886
954
|
}
|
|
955
|
+
function getHashKeys(keySchema) {
|
|
956
|
+
return keySchema.filter((k) => k.KeyType === "HASH").map((k) => k.AttributeName);
|
|
957
|
+
}
|
|
887
958
|
function getRangeKey(keySchema) {
|
|
888
959
|
const range = keySchema.find((k) => k.KeyType === "RANGE");
|
|
889
960
|
return range?.AttributeName;
|
|
890
961
|
}
|
|
962
|
+
function getRangeKeys(keySchema) {
|
|
963
|
+
return keySchema.filter((k) => k.KeyType === "RANGE").map((k) => k.AttributeName);
|
|
964
|
+
}
|
|
965
|
+
function hasCompleteKey(item, keySchema) {
|
|
966
|
+
return keySchema.every((element) => Boolean(item[element.AttributeName]));
|
|
967
|
+
}
|
|
968
|
+
function mergeKeySchemas(...schemas) {
|
|
969
|
+
const merged = [];
|
|
970
|
+
for (const schema of schemas) {
|
|
971
|
+
for (const element of schema) {
|
|
972
|
+
if (!merged.some((existing) => existing.AttributeName === element.AttributeName)) {
|
|
973
|
+
merged.push(element);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return merged;
|
|
978
|
+
}
|
|
891
979
|
function deepClone(obj) {
|
|
892
980
|
return JSON.parse(JSON.stringify(obj));
|
|
893
981
|
}
|
|
@@ -897,8 +985,10 @@ function estimateItemSize(item) {
|
|
|
897
985
|
|
|
898
986
|
// src/expressions/key-condition.ts
|
|
899
987
|
function parseKeyCondition(expression, keySchema, context) {
|
|
900
|
-
const
|
|
901
|
-
const
|
|
988
|
+
const hashKeyNames = getHashKeys(keySchema);
|
|
989
|
+
const rangeKeyNames = getRangeKeys(keySchema);
|
|
990
|
+
const hashKeyName = hashKeyNames[0];
|
|
991
|
+
const rangeKeyName = rangeKeyNames[0];
|
|
902
992
|
const resolvedNames = context.expressionAttributeNames || {};
|
|
903
993
|
const resolvedValues = context.expressionAttributeValues || {};
|
|
904
994
|
function resolveName(name) {
|
|
@@ -943,17 +1033,17 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
943
1033
|
return s;
|
|
944
1034
|
}
|
|
945
1035
|
const normalizedExpression = stripOuterParens(expression);
|
|
946
|
-
const parts = normalizedExpression
|
|
947
|
-
|
|
948
|
-
|
|
1036
|
+
const parts = splitKeyConditions(normalizedExpression);
|
|
1037
|
+
const hashConditions = /* @__PURE__ */ new Map();
|
|
1038
|
+
const rangeConditions = /* @__PURE__ */ new Map();
|
|
949
1039
|
for (const part of parts) {
|
|
950
1040
|
const trimmed = stripOuterParens(part);
|
|
951
1041
|
const beginsWithMatch = trimmed.match(/^begins_with\s*\(\s*([#\w]+)\s*,\s*(:\w+)\s*\)$/i);
|
|
952
1042
|
if (beginsWithMatch) {
|
|
953
1043
|
const attrName = resolveName(beginsWithMatch[1]);
|
|
954
1044
|
const value = resolveValue(beginsWithMatch[2]);
|
|
955
|
-
if (attrName
|
|
956
|
-
|
|
1045
|
+
if (rangeKeyNames.includes(attrName)) {
|
|
1046
|
+
rangeConditions.set(attrName, { operator: "begins_with", value });
|
|
957
1047
|
} else {
|
|
958
1048
|
throw new ValidationException(`begins_with can only be used on sort key`);
|
|
959
1049
|
}
|
|
@@ -964,8 +1054,8 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
964
1054
|
const attrName = resolveName(betweenMatch[1]);
|
|
965
1055
|
const value1 = resolveValue(betweenMatch[2]);
|
|
966
1056
|
const value2 = resolveValue(betweenMatch[3]);
|
|
967
|
-
if (attrName
|
|
968
|
-
|
|
1057
|
+
if (rangeKeyNames.includes(attrName)) {
|
|
1058
|
+
rangeConditions.set(attrName, { operator: "BETWEEN", value: value1, value2 });
|
|
969
1059
|
} else {
|
|
970
1060
|
throw new ValidationException(`BETWEEN can only be used on sort key`);
|
|
971
1061
|
}
|
|
@@ -976,13 +1066,13 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
976
1066
|
const attrName = resolveName(comparisonMatch[1]);
|
|
977
1067
|
const operator = comparisonMatch[2];
|
|
978
1068
|
const value = resolveValue(comparisonMatch[3]);
|
|
979
|
-
if (attrName
|
|
1069
|
+
if (hashKeyNames.includes(attrName)) {
|
|
980
1070
|
if (operator !== "=") {
|
|
981
|
-
throw new ValidationException(`
|
|
1071
|
+
throw new ValidationException(`Partition key condition must use = operator`);
|
|
982
1072
|
}
|
|
983
|
-
|
|
984
|
-
} else if (attrName
|
|
985
|
-
|
|
1073
|
+
hashConditions.set(attrName, value);
|
|
1074
|
+
} else if (rangeKeyNames.includes(attrName)) {
|
|
1075
|
+
rangeConditions.set(attrName, { operator, value });
|
|
986
1076
|
} else {
|
|
987
1077
|
throw new ValidationException(`Key condition references unknown attribute: ${attrName}`);
|
|
988
1078
|
}
|
|
@@ -990,30 +1080,112 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
990
1080
|
}
|
|
991
1081
|
throw new ValidationException(`Invalid key condition expression: ${trimmed}`);
|
|
992
1082
|
}
|
|
993
|
-
|
|
994
|
-
|
|
1083
|
+
for (const key of hashKeyNames) {
|
|
1084
|
+
if (!hashConditions.has(key)) {
|
|
1085
|
+
throw new ValidationException(`Key condition must specify equality for partition key ${key}`);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
let lastSortKeyIndex = -1;
|
|
1089
|
+
for (const [index, key] of rangeKeyNames.entries()) {
|
|
1090
|
+
const condition = rangeConditions.get(key);
|
|
1091
|
+
if (!condition) {
|
|
1092
|
+
if (lastSortKeyIndex !== -1) {
|
|
1093
|
+
throw new ValidationException(`Sort key conditions must reference a contiguous prefix of the key schema`);
|
|
1094
|
+
}
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (index > 0 && !rangeConditions.has(rangeKeyNames[index - 1])) {
|
|
1098
|
+
throw new ValidationException(`Sort key conditions must reference a contiguous prefix of the key schema`);
|
|
1099
|
+
}
|
|
1100
|
+
if (lastSortKeyIndex !== -1) {
|
|
1101
|
+
const previous = rangeConditions.get(rangeKeyNames[lastSortKeyIndex]);
|
|
1102
|
+
if (previous && previous.operator !== "=") {
|
|
1103
|
+
throw new ValidationException(`Only the last sort key condition can use a range operator`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
lastSortKeyIndex = index;
|
|
995
1107
|
}
|
|
1108
|
+
const orderedHashConditions = hashKeyNames.map((key) => ({
|
|
1109
|
+
key,
|
|
1110
|
+
value: hashConditions.get(key)
|
|
1111
|
+
}));
|
|
1112
|
+
const orderedRangeConditions = rangeKeyNames.filter((key) => rangeConditions.has(key)).map((key) => {
|
|
1113
|
+
const condition = rangeConditions.get(key);
|
|
1114
|
+
return {
|
|
1115
|
+
key,
|
|
1116
|
+
operator: condition.operator,
|
|
1117
|
+
value: condition.value,
|
|
1118
|
+
value2: condition.value2
|
|
1119
|
+
};
|
|
1120
|
+
});
|
|
996
1121
|
return {
|
|
997
1122
|
hashKey: hashKeyName,
|
|
998
|
-
hashValue,
|
|
1123
|
+
hashValue: hashConditions.get(hashKeyName),
|
|
999
1124
|
rangeKey: rangeKeyName,
|
|
1000
|
-
rangeCondition
|
|
1125
|
+
rangeCondition: orderedRangeConditions[0] ? {
|
|
1126
|
+
operator: orderedRangeConditions[0].operator,
|
|
1127
|
+
value: orderedRangeConditions[0].value,
|
|
1128
|
+
value2: orderedRangeConditions[0].value2
|
|
1129
|
+
} : void 0,
|
|
1130
|
+
hashConditions: orderedHashConditions,
|
|
1131
|
+
rangeConditions: orderedRangeConditions
|
|
1001
1132
|
};
|
|
1002
1133
|
}
|
|
1003
1134
|
function matchesKeyCondition(item, condition) {
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1135
|
+
for (const hashCondition of condition.hashConditions) {
|
|
1136
|
+
const itemHashValue = item[hashCondition.key];
|
|
1137
|
+
if (!itemHashValue || !attributeEquals(itemHashValue, hashCondition.value)) {
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1007
1140
|
}
|
|
1008
|
-
|
|
1009
|
-
const itemRangeValue = item[
|
|
1141
|
+
for (const rangeCondition of condition.rangeConditions) {
|
|
1142
|
+
const itemRangeValue = item[rangeCondition.key];
|
|
1010
1143
|
if (!itemRangeValue) {
|
|
1011
1144
|
return false;
|
|
1012
1145
|
}
|
|
1013
|
-
|
|
1146
|
+
if (!matchesRangeCondition(itemRangeValue, rangeCondition)) {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1014
1149
|
}
|
|
1015
1150
|
return true;
|
|
1016
1151
|
}
|
|
1152
|
+
function splitKeyConditions(expression) {
|
|
1153
|
+
const parts = [];
|
|
1154
|
+
let depth = 0;
|
|
1155
|
+
let segmentStart = 0;
|
|
1156
|
+
let betweenPending = false;
|
|
1157
|
+
for (let index = 0; index < expression.length; index++) {
|
|
1158
|
+
const char = expression[index];
|
|
1159
|
+
if (char === "(") {
|
|
1160
|
+
depth++;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (char === ")") {
|
|
1164
|
+
depth--;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
if (depth !== 0) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (/^BETWEEN\b/i.test(expression.slice(index))) {
|
|
1171
|
+
betweenPending = true;
|
|
1172
|
+
index += "BETWEEN".length - 1;
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
if (/^\s+AND\s+/i.test(expression.slice(index))) {
|
|
1176
|
+
if (betweenPending) {
|
|
1177
|
+
betweenPending = false;
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
parts.push(expression.slice(segmentStart, index).trim());
|
|
1181
|
+
const andMatch = expression.slice(index).match(/^\s+AND\s+/i);
|
|
1182
|
+
index += andMatch[0].length - 1;
|
|
1183
|
+
segmentStart = index + 1;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
parts.push(expression.slice(segmentStart).trim());
|
|
1187
|
+
return parts.filter(Boolean);
|
|
1188
|
+
}
|
|
1017
1189
|
function matchesRangeCondition(value, condition) {
|
|
1018
1190
|
const cmp5 = compareValues2(value, condition.value);
|
|
1019
1191
|
switch (condition.operator) {
|
|
@@ -1634,9 +1806,10 @@ function query(store, input) {
|
|
|
1634
1806
|
let items;
|
|
1635
1807
|
let lastEvaluatedKey;
|
|
1636
1808
|
if (input.IndexName) {
|
|
1809
|
+
const hashValues = Object.fromEntries(keyCondition.hashConditions.map((condition) => [condition.key, condition.value]));
|
|
1637
1810
|
const result = table.queryIndex(
|
|
1638
1811
|
input.IndexName,
|
|
1639
|
-
|
|
1812
|
+
hashValues,
|
|
1640
1813
|
{
|
|
1641
1814
|
scanIndexForward: input.ScanIndexForward,
|
|
1642
1815
|
exclusiveStartKey: input.ExclusiveStartKey
|
|
@@ -1645,8 +1818,9 @@ function query(store, input) {
|
|
|
1645
1818
|
items = result.items;
|
|
1646
1819
|
lastEvaluatedKey = result.lastEvaluatedKey;
|
|
1647
1820
|
} else {
|
|
1821
|
+
const hashValues = Object.fromEntries(keyCondition.hashConditions.map((condition) => [condition.key, condition.value]));
|
|
1648
1822
|
const result = table.queryByHashKey(
|
|
1649
|
-
|
|
1823
|
+
hashValues,
|
|
1650
1824
|
{
|
|
1651
1825
|
scanIndexForward: input.ScanIndexForward,
|
|
1652
1826
|
exclusiveStartKey: input.ExclusiveStartKey
|
|
@@ -1671,24 +1845,11 @@ function query(store, input) {
|
|
|
1671
1845
|
items = items.slice(0, input.Limit);
|
|
1672
1846
|
if (items.length > 0) {
|
|
1673
1847
|
const lastItem = items[items.length - 1];
|
|
1674
|
-
lastEvaluatedKey =
|
|
1675
|
-
const hashKey = table.getHashKeyName();
|
|
1676
|
-
const rangeKey = table.getRangeKeyName();
|
|
1677
|
-
if (lastItem[hashKey]) {
|
|
1678
|
-
lastEvaluatedKey[hashKey] = lastItem[hashKey];
|
|
1679
|
-
}
|
|
1680
|
-
if (rangeKey && lastItem[rangeKey]) {
|
|
1681
|
-
lastEvaluatedKey[rangeKey] = lastItem[rangeKey];
|
|
1682
|
-
}
|
|
1848
|
+
lastEvaluatedKey = extractKey(lastItem, table.keySchema);
|
|
1683
1849
|
if (input.IndexName) {
|
|
1684
1850
|
const indexKeySchema = table.getIndexKeySchema(input.IndexName);
|
|
1685
1851
|
if (indexKeySchema) {
|
|
1686
|
-
|
|
1687
|
-
const attrValue = lastItem[key.AttributeName];
|
|
1688
|
-
if (attrValue) {
|
|
1689
|
-
lastEvaluatedKey[key.AttributeName] = attrValue;
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1852
|
+
lastEvaluatedKey = extractKey(lastItem, mergeKeySchemas(indexKeySchema, table.keySchema));
|
|
1692
1853
|
}
|
|
1693
1854
|
}
|
|
1694
1855
|
}
|
|
@@ -1755,24 +1916,11 @@ function scan(store, input) {
|
|
|
1755
1916
|
items = items.slice(0, input.Limit);
|
|
1756
1917
|
if (items.length > 0) {
|
|
1757
1918
|
const lastItem = items[items.length - 1];
|
|
1758
|
-
lastEvaluatedKey =
|
|
1759
|
-
const hashKey = table.getHashKeyName();
|
|
1760
|
-
const rangeKey = table.getRangeKeyName();
|
|
1761
|
-
if (lastItem[hashKey]) {
|
|
1762
|
-
lastEvaluatedKey[hashKey] = lastItem[hashKey];
|
|
1763
|
-
}
|
|
1764
|
-
if (rangeKey && lastItem[rangeKey]) {
|
|
1765
|
-
lastEvaluatedKey[rangeKey] = lastItem[rangeKey];
|
|
1766
|
-
}
|
|
1919
|
+
lastEvaluatedKey = extractKey(lastItem, table.keySchema);
|
|
1767
1920
|
if (input.IndexName) {
|
|
1768
1921
|
const indexKeySchema = table.getIndexKeySchema(input.IndexName);
|
|
1769
1922
|
if (indexKeySchema) {
|
|
1770
|
-
|
|
1771
|
-
const attrValue = lastItem[key.AttributeName];
|
|
1772
|
-
if (attrValue) {
|
|
1773
|
-
lastEvaluatedKey[key.AttributeName] = attrValue;
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1923
|
+
lastEvaluatedKey = extractKey(lastItem, mergeKeySchemas(indexKeySchema, table.keySchema));
|
|
1776
1924
|
}
|
|
1777
1925
|
}
|
|
1778
1926
|
}
|
|
@@ -2569,12 +2717,7 @@ var Table = class {
|
|
|
2569
2717
|
}
|
|
2570
2718
|
}
|
|
2571
2719
|
buildIndexKey(item, keySchema) {
|
|
2572
|
-
|
|
2573
|
-
if (!item[hashAttr]) {
|
|
2574
|
-
return null;
|
|
2575
|
-
}
|
|
2576
|
-
const rangeAttr = getRangeKey(keySchema);
|
|
2577
|
-
if (rangeAttr && !item[rangeAttr]) {
|
|
2720
|
+
if (!hasCompleteKey(item, keySchema)) {
|
|
2578
2721
|
return null;
|
|
2579
2722
|
}
|
|
2580
2723
|
return serializeKey(extractKey(item, keySchema), keySchema);
|
|
@@ -2620,47 +2763,7 @@ var Table = class {
|
|
|
2620
2763
|
};
|
|
2621
2764
|
}
|
|
2622
2765
|
queryByHashKey(hashValue, options) {
|
|
2623
|
-
|
|
2624
|
-
const rangeAttr = this.getRangeKeyName();
|
|
2625
|
-
const matchingItems = [];
|
|
2626
|
-
for (const item of this.items.values()) {
|
|
2627
|
-
const itemHashValue = item[hashAttr];
|
|
2628
|
-
const queryHashValue = hashValue[hashAttr];
|
|
2629
|
-
if (itemHashValue && queryHashValue && this.attributeEquals(itemHashValue, queryHashValue)) {
|
|
2630
|
-
matchingItems.push(deepClone(item));
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
if (rangeAttr) {
|
|
2634
|
-
matchingItems.sort((a, b) => {
|
|
2635
|
-
const aVal = a[rangeAttr];
|
|
2636
|
-
const bVal = b[rangeAttr];
|
|
2637
|
-
if (!aVal && !bVal) return 0;
|
|
2638
|
-
if (!aVal) return 1;
|
|
2639
|
-
if (!bVal) return -1;
|
|
2640
|
-
const cmp5 = this.compareAttributes(aVal, bVal);
|
|
2641
|
-
return options?.scanIndexForward === false ? -cmp5 : cmp5;
|
|
2642
|
-
});
|
|
2643
|
-
}
|
|
2644
|
-
let startIdx = 0;
|
|
2645
|
-
if (options?.exclusiveStartKey) {
|
|
2646
|
-
const startKey = serializeKey(options.exclusiveStartKey, this.keySchema);
|
|
2647
|
-
startIdx = matchingItems.findIndex(
|
|
2648
|
-
(item) => serializeKey(extractKey(item, this.keySchema), this.keySchema) === startKey
|
|
2649
|
-
);
|
|
2650
|
-
if (startIdx !== -1) {
|
|
2651
|
-
startIdx++;
|
|
2652
|
-
} else {
|
|
2653
|
-
startIdx = 0;
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
const limit = options?.limit;
|
|
2657
|
-
const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
|
|
2658
|
-
const hasMore = limit ? startIdx + limit < matchingItems.length : false;
|
|
2659
|
-
const lastItem = sliced[sliced.length - 1];
|
|
2660
|
-
return {
|
|
2661
|
-
items: sliced,
|
|
2662
|
-
lastEvaluatedKey: hasMore && lastItem ? extractKey(lastItem, this.keySchema) : void 0
|
|
2663
|
-
};
|
|
2766
|
+
return this.queryBySchema(this.keySchema, hashValue, this.keySchema, false, options);
|
|
2664
2767
|
}
|
|
2665
2768
|
queryIndex(indexName, hashValue, options) {
|
|
2666
2769
|
const gsi = this.globalSecondaryIndexes.get(indexName);
|
|
@@ -2669,69 +2772,16 @@ var Table = class {
|
|
|
2669
2772
|
if (!indexData) {
|
|
2670
2773
|
throw new Error(`Index ${indexName} not found`);
|
|
2671
2774
|
}
|
|
2672
|
-
const
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
continue;
|
|
2680
|
-
}
|
|
2681
|
-
if (indexRangeAttr && !item[indexRangeAttr]) {
|
|
2682
|
-
continue;
|
|
2683
|
-
}
|
|
2684
|
-
matchingItems.push(deepClone(item));
|
|
2685
|
-
}
|
|
2686
|
-
if (indexRangeAttr) {
|
|
2687
|
-
matchingItems.sort((a, b) => {
|
|
2688
|
-
const aVal = a[indexRangeAttr];
|
|
2689
|
-
const bVal = b[indexRangeAttr];
|
|
2690
|
-
if (!aVal && !bVal) return 0;
|
|
2691
|
-
if (!aVal) return 1;
|
|
2692
|
-
if (!bVal) return -1;
|
|
2693
|
-
const cmp5 = this.compareAttributes(aVal, bVal);
|
|
2694
|
-
return options?.scanIndexForward === false ? -cmp5 : cmp5;
|
|
2695
|
-
});
|
|
2696
|
-
}
|
|
2697
|
-
let startIdx = 0;
|
|
2698
|
-
if (options?.exclusiveStartKey) {
|
|
2699
|
-
const combinedKeySchema = [...indexData.keySchema];
|
|
2700
|
-
const tableRangeKey = this.getRangeKeyName();
|
|
2701
|
-
if (tableRangeKey && !combinedKeySchema.some((k) => k.AttributeName === tableRangeKey)) {
|
|
2702
|
-
combinedKeySchema.push({ AttributeName: tableRangeKey, KeyType: "RANGE" });
|
|
2703
|
-
}
|
|
2704
|
-
const tableHashKey = this.getHashKeyName();
|
|
2705
|
-
if (!combinedKeySchema.some((k) => k.AttributeName === tableHashKey)) {
|
|
2706
|
-
combinedKeySchema.push({ AttributeName: tableHashKey, KeyType: "HASH" });
|
|
2707
|
-
}
|
|
2708
|
-
const startKey = serializeKey(options.exclusiveStartKey, combinedKeySchema);
|
|
2709
|
-
startIdx = matchingItems.findIndex(
|
|
2710
|
-
(item) => serializeKey(extractKey(item, combinedKeySchema), combinedKeySchema) === startKey
|
|
2711
|
-
);
|
|
2712
|
-
if (startIdx !== -1) {
|
|
2713
|
-
startIdx++;
|
|
2714
|
-
} else {
|
|
2715
|
-
startIdx = 0;
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
const limit = options?.limit;
|
|
2719
|
-
const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
|
|
2720
|
-
const hasMore = limit ? startIdx + limit < matchingItems.length : false;
|
|
2721
|
-
const lastItem = sliced[sliced.length - 1];
|
|
2722
|
-
let lastEvaluatedKey;
|
|
2723
|
-
if (hasMore && lastItem) {
|
|
2724
|
-
lastEvaluatedKey = extractKey(lastItem, this.keySchema);
|
|
2725
|
-
for (const keyElement of indexData.keySchema) {
|
|
2726
|
-
const attrValue = lastItem[keyElement.AttributeName];
|
|
2727
|
-
if (attrValue) {
|
|
2728
|
-
lastEvaluatedKey[keyElement.AttributeName] = attrValue;
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2775
|
+
const result = this.queryBySchema(
|
|
2776
|
+
indexData.keySchema,
|
|
2777
|
+
hashValue,
|
|
2778
|
+
mergeKeySchemas(indexData.keySchema, this.keySchema),
|
|
2779
|
+
true,
|
|
2780
|
+
options
|
|
2781
|
+
);
|
|
2732
2782
|
return {
|
|
2733
|
-
items:
|
|
2734
|
-
lastEvaluatedKey,
|
|
2783
|
+
items: result.items,
|
|
2784
|
+
lastEvaluatedKey: result.lastEvaluatedKey,
|
|
2735
2785
|
indexKeySchema: indexData.keySchema
|
|
2736
2786
|
};
|
|
2737
2787
|
}
|
|
@@ -2742,18 +2792,19 @@ var Table = class {
|
|
|
2742
2792
|
if (!indexData) {
|
|
2743
2793
|
throw new Error(`Index ${indexName} not found`);
|
|
2744
2794
|
}
|
|
2745
|
-
const indexHashAttr = getHashKey(indexData.keySchema);
|
|
2746
2795
|
const matchingItems = [];
|
|
2747
2796
|
for (const item of this.items.values()) {
|
|
2748
|
-
if (item
|
|
2797
|
+
if (hasCompleteKey(item, indexData.keySchema)) {
|
|
2749
2798
|
matchingItems.push(deepClone(item));
|
|
2750
2799
|
}
|
|
2751
2800
|
}
|
|
2801
|
+
matchingItems.sort((a, b) => this.compareByKeySchema(a, b, indexData.keySchema, true));
|
|
2752
2802
|
let startIdx = 0;
|
|
2753
2803
|
if (exclusiveStartKey) {
|
|
2754
|
-
const
|
|
2804
|
+
const paginationKeySchema = mergeKeySchemas(indexData.keySchema, this.keySchema);
|
|
2805
|
+
const startKey = serializeKey(exclusiveStartKey, paginationKeySchema);
|
|
2755
2806
|
startIdx = matchingItems.findIndex(
|
|
2756
|
-
(item) => serializeKey(extractKey(item,
|
|
2807
|
+
(item) => serializeKey(extractKey(item, paginationKeySchema), paginationKeySchema) === startKey
|
|
2757
2808
|
);
|
|
2758
2809
|
if (startIdx !== -1) {
|
|
2759
2810
|
startIdx++;
|
|
@@ -2766,13 +2817,7 @@ var Table = class {
|
|
|
2766
2817
|
const lastItem = sliced[sliced.length - 1];
|
|
2767
2818
|
let lastEvaluatedKey;
|
|
2768
2819
|
if (hasMore && lastItem) {
|
|
2769
|
-
lastEvaluatedKey = extractKey(lastItem, this.keySchema);
|
|
2770
|
-
for (const keyElement of indexData.keySchema) {
|
|
2771
|
-
const attrValue = lastItem[keyElement.AttributeName];
|
|
2772
|
-
if (attrValue) {
|
|
2773
|
-
lastEvaluatedKey[keyElement.AttributeName] = attrValue;
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2820
|
+
lastEvaluatedKey = extractKey(lastItem, mergeKeySchemas(indexData.keySchema, this.keySchema));
|
|
2776
2821
|
}
|
|
2777
2822
|
return {
|
|
2778
2823
|
items: sliced,
|
|
@@ -2798,6 +2843,67 @@ var Table = class {
|
|
|
2798
2843
|
if ("B" in a && "B" in b) return a.B.localeCompare(b.B);
|
|
2799
2844
|
return 0;
|
|
2800
2845
|
}
|
|
2846
|
+
compareByKeySchema(a, b, keySchema, scanIndexForward = true) {
|
|
2847
|
+
for (const attributeName of [...getHashKeys(keySchema), ...getRangeKeys(keySchema)]) {
|
|
2848
|
+
const aVal = a[attributeName];
|
|
2849
|
+
const bVal = b[attributeName];
|
|
2850
|
+
if (!aVal && !bVal) {
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
if (!aVal) {
|
|
2854
|
+
return 1;
|
|
2855
|
+
}
|
|
2856
|
+
if (!bVal) {
|
|
2857
|
+
return -1;
|
|
2858
|
+
}
|
|
2859
|
+
const cmp5 = this.compareAttributes(aVal, bVal);
|
|
2860
|
+
if (cmp5 !== 0) {
|
|
2861
|
+
return scanIndexForward ? cmp5 : -cmp5;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
return 0;
|
|
2865
|
+
}
|
|
2866
|
+
queryBySchema(keySchema, hashValue, paginationKeySchema, requireCompleteKey, options) {
|
|
2867
|
+
const hashAttrs = getHashKeys(keySchema);
|
|
2868
|
+
const rangeAttrs = getRangeKeys(keySchema);
|
|
2869
|
+
const matchingItems = [];
|
|
2870
|
+
for (const item of this.items.values()) {
|
|
2871
|
+
if (requireCompleteKey && !hasCompleteKey(item, keySchema)) {
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
const matchesPartitionKey = hashAttrs.every((attr) => {
|
|
2875
|
+
const itemHashValue = item[attr];
|
|
2876
|
+
const queryHashValue = hashValue[attr];
|
|
2877
|
+
return itemHashValue && queryHashValue && this.attributeEquals(itemHashValue, queryHashValue);
|
|
2878
|
+
});
|
|
2879
|
+
if (matchesPartitionKey) {
|
|
2880
|
+
matchingItems.push(deepClone(item));
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
if (rangeAttrs.length > 0) {
|
|
2884
|
+
matchingItems.sort((a, b) => this.compareByKeySchema(a, b, keySchema, options?.scanIndexForward !== false));
|
|
2885
|
+
}
|
|
2886
|
+
let startIdx = 0;
|
|
2887
|
+
if (options?.exclusiveStartKey) {
|
|
2888
|
+
const startKey = serializeKey(options.exclusiveStartKey, paginationKeySchema);
|
|
2889
|
+
startIdx = matchingItems.findIndex(
|
|
2890
|
+
(item) => serializeKey(extractKey(item, paginationKeySchema), paginationKeySchema) === startKey
|
|
2891
|
+
);
|
|
2892
|
+
if (startIdx !== -1) {
|
|
2893
|
+
startIdx++;
|
|
2894
|
+
} else {
|
|
2895
|
+
startIdx = 0;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
const limit = options?.limit;
|
|
2899
|
+
const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
|
|
2900
|
+
const hasMore = limit ? startIdx + limit < matchingItems.length : false;
|
|
2901
|
+
const lastItem = sliced[sliced.length - 1];
|
|
2902
|
+
return {
|
|
2903
|
+
items: sliced,
|
|
2904
|
+
lastEvaluatedKey: hasMore && lastItem ? extractKey(lastItem, paginationKeySchema) : void 0
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2801
2907
|
getAllItems() {
|
|
2802
2908
|
return Array.from(this.items.values()).map((item) => deepClone(item));
|
|
2803
2909
|
}
|